diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ce3b0e..c34cdbdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,19 @@ ###Changelog +####Version 0.10.0 +* Pebble: option to send sunrise and sunset events to timeline +* Pebble: fix problems with unknown app keys while configuring watchfaces +* Mi Band: BLE connection fixes +* Fixes for enabling logging at whithout restarting Gadgetbridge +* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference) +* Display device address in device info + ####Version 0.9.8 * Pebble: fix more reconnnect issues * Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health * Pebble: option in AppManager to delete files from cache * Pebble: enable pbw cache and watchface configuration for Firmware 2.x * Pebble: allow enabling of Pebble Health without "untested features" being enabled +* Pebble: fix music information being messed up * Honour "Do Not Disturb" for phone calls and SMS ####Version 0.9.7 diff --git a/README.md b/README.md index 9367cd44..6f888ce8 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ need to create an account and transmit any of your data to the vendor's servers. 2. Start Gadgetbridge, tap on the device you want to connect to 3. To test, choose "Debug" from the menu and play around -For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Getting-Started-(Pebble)) +For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started) ## Features (Mi Band) @@ -103,10 +103,14 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue); just leave a comment that you're working on one to avoid duplicated work. -Please do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting. +Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or manually. -Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or -manually. +## Do you have further questions or feedback? + +Feel free to open an issue on our issue tracker, but please: +- do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting +- use the search functionality to ensure that your questions wasn't already answered. Don't forget to check the **closed** issues as well! +- remember that this is a community project, people are contributing in their free time because they like doing so: don't take the fun away! Be kind and constructive. ## Having problems? diff --git a/app/build.gradle b/app/build.gradle index c30ca034..f6cd378f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,8 @@ def ABORT_ON_CHECK_FAILURE=false tasks.withType(Test) { systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null) } +// sourceSets.test.runtimeClasspath += File('src/main/assets') + android { compileSdkVersion 23 buildToolsVersion "23.0.3" @@ -16,8 +18,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.9.8" - versionCode 52 + versionName "0.10.0" + versionCode 53 } buildTypes { release { @@ -42,6 +44,8 @@ android { } dependencies { +// testCompile 'ch.qos.logback:logback-classic:1.1.3' +// testCompile 'ch.qos.logback:logback-core:1.1.3' testCompile 'junit:junit:4.12' testCompile "org.mockito:mockito-core:1.9.5" @@ -54,6 +58,7 @@ dependencies { compile 'com.github.PhilJay:MPAndroidChart:v2.2.4' compile 'com.github.pfichtner:durationformatter:0.1.1' compile 'de.cketti.library.changelog:ckchangelog:1.2.2' + compile 'net.e175.klaus:solarpositioning:0.0.9' } check.dependsOn 'findbugs', 'pmd', 'lint' @@ -99,7 +104,6 @@ task pmd(type: Pmd) { } } - task findbugs(type: FindBugs) { ignoreFailures = !ABORT_ON_CHECK_FAILURE effort = "default" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 436e0423..46562de5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ + fileLogger; private static Prefs prefs; private static GBPrefs gbPrefs; /** @@ -72,6 +66,13 @@ public class GBApplication extends Application { public static final String ACTION_QUIT = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit"; + private static Logging logging = new Logging() { + @Override + protected String createLogDirectory() throws IOException { + File dir = FileUtils.getExternalFilesDir(); + return dir.getAbsolutePath(); + } + }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -114,11 +115,6 @@ public class GBApplication extends Application { } setupExceptionHandler(); -// For debugging problems with the logback configuration -// LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); -// print logback's internal status -// StatusPrinter.print(lc); -// Logger logger = LoggerFactory.getLogger(GBApplication.class); deviceService = createDeviceService(); GB.environment = GBEnvironment.createDeviceEnvironment(); @@ -138,6 +134,10 @@ public class GBApplication extends Application { // db.close(); } + public static void setupLogging(boolean enabled) { + logging.setupLogging(enabled); + } + private void setupExceptionHandler() { LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler()); Thread.setDefaultUncaughtExceptionHandler(handler); @@ -147,71 +147,6 @@ public class GBApplication extends Application { return prefs.getBoolean("log_to_file", false); } - public static void setupLogging(boolean enable) { - try { - if (fileLogger == null) { - File dir = FileUtils.getExternalFilesDir(); - // used by assets/logback.xml since the location cannot be statically determined - System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath()); - rememberFileLogger(); - } - if (enable) { - startFileLogger(); - } else { - stopFileLogger(); - } - getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME); - } catch (IOException ex) { - Log.e("GBApplication", "External files dir not available, cannot log to file", ex); - stopFileLogger(); - } - } - - private static void startFileLogger() { - if (fileLogger != null && !fileLogger.isStarted()) { - addFileLogger(fileLogger); - fileLogger.start(); - } - } - - private static void stopFileLogger() { - if (fileLogger != null && fileLogger.isStarted()) { - fileLogger.stop(); - removeFileLogger(fileLogger); - } - } - - private static void rememberFileLogger() { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - fileLogger = root.getAppender("FILE"); - } - - private static void addFileLogger(Appender fileLogger) { - try { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - if (!root.isAttached(fileLogger)) { - root.addAppender(fileLogger); - } - } catch (Throwable ex) { - Log.e("GBApplication", "Error adding logger FILE appender", ex); - } - } - - private static void removeFileLogger(Appender fileLogger) { - try { - ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - if (root.isAttached(fileLogger)) { - root.detachAppender(fileLogger); - } - } catch (Throwable ex) { - Log.e("GBApplication", "Error removing logger FILE appender", ex); - } - } - - private static Logger getLogger() { - return LoggerFactory.getLogger(GBApplication.class); - } - public static Context getContext() { return context; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java new file mode 100644 index 00000000..7ea3c8e9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java @@ -0,0 +1,128 @@ +package nodomain.freeyourgadget.gadgetbridge; + +import android.util.Log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.encoder.Encoder; +import ch.qos.logback.core.encoder.LayoutWrappingEncoder; +import ch.qos.logback.core.util.StatusPrinter; + +public abstract class Logging { + public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR"; + + private FileAppender fileLogger; + + public void setupLogging(boolean enable) { + try { + if (fileLogger == null) { + init(); + } + if (enable) { + startFileLogger(); + } else { + stopFileLogger(); + } + getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME); + } catch (IOException ex) { + Log.e("GBApplication", "External files dir not available, cannot log to file", ex); + stopFileLogger(); + } + } + + public void debugLoggingConfiguration() { + // For debugging problems with the logback configuration + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + // print logback's internal status + StatusPrinter.print(lc); +// Logger logger = LoggerFactory.getLogger(Logging.class); + } + + protected abstract String createLogDirectory() throws IOException; + + protected void init() throws IOException { + String dir = createLogDirectory(); + if (dir == null) { + throw new IllegalArgumentException("log directory must not be null"); + } + // used by assets/logback.xml since the location cannot be statically determined + System.setProperty(PROP_LOGFILES_DIR, dir); + rememberFileLogger(); + } + + private Logger getLogger() { + return LoggerFactory.getLogger(Logging.class); + } + + private void startFileLogger() { + if (fileLogger != null && !fileLogger.isStarted()) { + addFileLogger(fileLogger); + fileLogger.setLazy(false); // hack to make sure that start() actually opens the file + fileLogger.start(); + } + } + + private void stopFileLogger() { + if (fileLogger != null && fileLogger.isStarted()) { + fileLogger.stop(); + removeFileLogger(fileLogger); + } + } + + private void rememberFileLogger() { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + fileLogger = (FileAppender) root.getAppender("FILE"); + } + + private void addFileLogger(Appender fileLogger) { + try { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + if (!root.isAttached(fileLogger)) { + root.addAppender(fileLogger); + } + } catch (Throwable ex) { + Log.e("GBApplication", "Error adding logger FILE appender", ex); + } + } + + private void removeFileLogger(Appender fileLogger) { + try { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + if (root.isAttached(fileLogger)) { + root.detachAppender(fileLogger); + } + } catch (Throwable ex) { + Log.e("GBApplication", "Error removing logger FILE appender", ex); + } + } + + public FileAppender getFileLogger() { + return fileLogger; + } + + public boolean setImmediateFlush(boolean enable) { + FileAppender fileLogger = getFileLogger(); + Encoder encoder = fileLogger.getEncoder(); + if (encoder instanceof LayoutWrappingEncoder) { + ((LayoutWrappingEncoder) encoder).setImmediateFlush(enable); + return true; + } + return false; + } + + public boolean isImmediateFlush() { + FileAppender fileLogger = getFileLogger(); + Encoder encoder = fileLogger.getEncoder(); + if (encoder instanceof LayoutWrappingEncoder) { + return ((LayoutWrappingEncoder) encoder).isImmediateFlush(); + } + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java index 702aa134..37de8193 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java @@ -353,11 +353,7 @@ public class ControlCenter extends GBActivity { } private void launchDiscoveryActivity() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS)); - } else { - startActivity(new Intent(this, DiscoveryActivity.class)); - } + startActivity(new Intent(this, DiscoveryActivity.class)); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index d7d81f69..c832148f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -20,15 +20,20 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Toast; +import net.e175.klaus.solarpositioning.DeltaT; +import net.e175.klaus.solarpositioning.SPA; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.util.GregorianCalendar; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -36,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class DebugActivity extends GBActivity { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java index f2ab531b..3f00ced4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java @@ -1,5 +1,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities; +import android.Manifest; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -8,10 +9,12 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Parcelable; +import android.support.v4.app.ActivityCompat; import android.view.View; import android.widget.AdapterView; import android.widget.Button; @@ -48,6 +51,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: // continue with LE scan, if available if (isScanning == Scanning.SCANNING_BT) { + checkAndRequestLocationPermission(); startDiscovery(Scanning.SCANNING_BTLE); } else { discoveryFinished(); @@ -320,6 +324,12 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC adapter.startDiscovery(); } + private void checkAndRequestLocationPermission() { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0); + } + } + private Message getPostMessage(Runnable runnable) { Message m = Message.obtain(handler, runnable); m.obj = runnable; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java index 7dba008b..a5181dcc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.Iterator; +import java.util.Scanner; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -138,19 +139,36 @@ public class ExternalPebbleJSActivity extends GBActivity { try { JSONObject in = new JSONObject(msg); JSONObject out = new JSONObject(); - String cur_key; + String inKey, outKey; + boolean passKey = false; for (Iterator key = in.keys(); key.hasNext(); ) { - cur_key = key.next(); - int pebbleAppIndex = knownKeys.optInt(cur_key); + passKey = false; + inKey = key.next(); + outKey = null; + int pebbleAppIndex = knownKeys.optInt(inKey); if (pebbleAppIndex != 0) { - Object obj = in.get(cur_key); + passKey = true; + outKey = String.valueOf(pebbleAppIndex); + + } else { + //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ ) + Scanner scanner = new Scanner(inKey); + if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) { + passKey = true; + outKey = inKey; + } + } + + if (passKey && outKey != null) { + Object obj = in.get(inKey); if (obj instanceof Boolean) { obj = ((Boolean) obj) ? "true" : "false"; } - out.put(String.valueOf(pebbleAppIndex), obj); + out.put(outKey, obj); } else { - GB.toast("Discarded key " + cur_key + ", not found in the local configuration.", Toast.LENGTH_SHORT, GB.WARN); + GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); } + } LOG.info(out.toString()); GBApplication.deviceService().onAppConfiguration(appUuid, out.toString()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java index 39c972ea..dd9c2599 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java @@ -1,14 +1,33 @@ package nodomain.freeyourgadget.gadgetbridge.activities; +import android.content.res.Configuration; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import java.util.Locale; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class GBActivity extends AppCompatActivity { + private void setLanguage(String language) { + Locale locale; + if (language.equals("default")) { + locale = Locale.getDefault(); + } else { + locale = new Locale(language); + } + Configuration config = new Configuration(); + config.locale = locale; + + // FIXME: I have no idea what I am doing + getApplicationContext().getResources().updateConfiguration(config, getApplicationContext().getResources().getDisplayMetrics()); + getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics()); + } + @Override protected void onCreate(Bundle savedInstanceState) { if (GBApplication.isDarkThemeEnabled()) { @@ -17,6 +36,9 @@ public class GBActivity extends AppCompatActivity { setTheme(R.style.GadgetbridgeTheme); } + Prefs prefs = GBApplication.getPrefs(); + String language = prefs.getString("language", "default"); + setLanguage(language); super.onCreate(savedInstanceState); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index c6479695..41d6bc55 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -1,17 +1,28 @@ package nodomain.freeyourgadget.gadgetbridge.activities; +import android.Manifest; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationManager; import android.os.Bundle; +import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; +import android.support.v4.app.ActivityCompat; import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.util.List; +import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; @@ -19,13 +30,14 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActi import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; -import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG; import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH; public class SettingsActivity extends AbstractSettingsActivity { + private static final Logger LOG = LoggerFactory.getLogger(SettingsActivity.class); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -114,6 +126,34 @@ public class SettingsActivity extends AbstractSettingsActivity { category.removePreference(pref); } + pref = findPreference("location_aquire"); + pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(SettingsActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0); + } + + LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + Criteria criteria = new Criteria(); + String provider = locationManager.getBestProvider(criteria, false); + if (provider != null) { + Location location = locationManager.getLastKnownLocation(provider); + String latitude = String.format(Locale.US, "%.6g", location.getLatitude()); + String longitude = String.format(Locale.US, "%.6g", location.getLongitude()); + LOG.info("got location. Lat: " + latitude + " Lng: " + longitude); + EditTextPreference pref_latitude = (EditTextPreference) findPreference("location_latitude"); + EditTextPreference pref_longitude = (EditTextPreference) findPreference("location_longitude"); + pref_latitude.setText(latitude); + pref_longitude.setText(longitude); + pref_latitude.setSummary(latitude); + pref_longitude.setSummary(longitude); + } else { + LOG.warn("No location provider found, did you deny location permission?"); + } + return true; + } + }); + // Get all receivers of Media Buttons Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); @@ -146,6 +186,8 @@ public class SettingsActivity extends AbstractSettingsActivity { "pebble_emu_addr", "pebble_emu_port", "pebble_reconnect_attempts", + "location_latitude", + "location_longitude", "canned_reply_suffix", "canned_reply_1", "canned_reply_2", diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index a6dd21bf..602ca5c8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.UUID; 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.NotificationSpec; @@ -51,4 +52,8 @@ public interface EventHandler { void onScreenshotReq(); void onEnableHeartRateSleepSupport(boolean enable); + + void onAddCalendarEvent(CalendarEventSpec calendarEventSpec); + + void onDeleteCalendarEvent(byte type, long id); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java index 8379d75b..ca8b9e64 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java @@ -16,6 +16,7 @@ public final class MiBandConst { public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer"; public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar"; public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection"; + public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours"; public static final String ORIGIN_SMS = "sms"; @@ -27,6 +28,7 @@ public final class MiBandConst { public static final String MI_1A = "1A"; public static final String MI_1S = "1S"; public static final String MI_AMAZFIT = "Amazfit"; + public static final String MI_PRO = "2"; public static int getNotificationPrefIntValue(String pref, String origin, Prefs prefs, int defaultValue) { String key = getNotificationPrefKey(pref, origin); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index c4e8e83c..505d3530 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -140,6 +140,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator { return location; } + public static int getDeviceTimeOffsetHours() throws IllegalArgumentException { + Prefs prefs = GBApplication.getPrefs(); + return prefs.getInt(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, 0); + } + public static boolean getHeartrateSleepSupport(String miBandAddress) throws IllegalArgumentException { Prefs prefs = GBApplication.getPrefs(); return prefs.getBoolean(MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION, false); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java index 88a5e230..e41cf9be 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java @@ -40,6 +40,10 @@ public class MiBandDateConverter { value[offset + 4], value[offset + 5]); + int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(); + if(offsetInHours != 0) + timestamp.add(Calendar.HOUR_OF_DAY,-offsetInHours); + return timestamp; } @@ -53,6 +57,18 @@ public class MiBandDateConverter { * @return */ public static byte[] calendarToRawBytes(Calendar timestamp) { + + // The mi-band device currently records sleep + // only if it happens after 10pm and before 7am. + // The offset is used to trick the device to record sleep + // in non-standard hours. + // If you usually sleep, say, from 6am to 2pm, set the + // shift to -8, so at 6am the device thinks it's still 10pm + // of the day before. + int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours(); + if(offsetInHours != 0) + timestamp.add(Calendar.HOUR_OF_DAY,offsetInHours); + return new byte[]{ (byte) (timestamp.get(Calendar.YEAR) - 2000), (byte) timestamp.get(Calendar.MONTH), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java index e49ae1ab..7fb29ce9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java @@ -16,6 +16,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.OR import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_PEBBLEMSG; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_SMS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS; +import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR; @@ -62,6 +63,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity { PREF_MIBAND_ADDRESS, PREF_MIBAND_FITNESS_GOAL, PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, + PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL), getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL), diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java new file mode 100644 index 00000000..e9470d31 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmReceiver.java @@ -0,0 +1,89 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import net.e175.klaus.solarpositioning.DeltaT; +import net.e175.klaus.solarpositioning.SPA; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class AlarmReceiver extends BroadcastReceiver { + private static final Logger LOG = LoggerFactory.getLogger(AlarmReceiver.class); + + public AlarmReceiver() { + Context context = GBApplication.getContext(); + Intent intent = new Intent("DAILY_ALARM"); + intent.setPackage(BuildConfig.APPLICATION_ID); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent("DAILY_ALARM"), 0); + AlarmManager am = (AlarmManager) (context.getSystemService(Context.ALARM_SERVICE)); + + am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_DAY, pendingIntent); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!GBApplication.getPrefs().getBoolean("send_sunrise_sunset", false)) { + LOG.info("won't send sunrise and sunset events (disabled in preferences)"); + return; + } + LOG.info("will resend sunrise and sunset events"); + + final GregorianCalendar dateTimeTomorrow = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + dateTimeTomorrow.set(Calendar.HOUR, 0); + dateTimeTomorrow.set(Calendar.MINUTE, 0); + dateTimeTomorrow.set(Calendar.SECOND, 0); + dateTimeTomorrow.set(Calendar.MILLISECOND, 0); + dateTimeTomorrow.add(GregorianCalendar.DAY_OF_MONTH, 1); + + /* + * rotate ids ud reuse the id from two days ago for tomorrow, this way we will have + * sunrise /sunset for 3 days while sending only sunrise/sunset per day + */ + byte id_tomorrow = (byte) ((dateTimeTomorrow.getTimeInMillis() / (1000L * 60L * 60L * 24L)) % 3); + + GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNRISE, id_tomorrow); + GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNSET, id_tomorrow); + + Prefs prefs = GBApplication.getPrefs(); + + float latitude = prefs.getFloat("location_latitude", 0); + float longitude = prefs.getFloat("location_longitude", 0); + LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude); + GregorianCalendar[] sunriseTransitSetTomorrow = SPA.calculateSunriseTransitSet(dateTimeTomorrow, latitude, longitude, DeltaT.estimate(dateTimeTomorrow)); + + CalendarEventSpec calendarEventSpec = new CalendarEventSpec(); + calendarEventSpec.durationInSeconds = 0; + calendarEventSpec.description = null; + + calendarEventSpec.type = CalendarEventSpec.TYPE_SUNRISE; + calendarEventSpec.title = "Sunrise"; + if (sunriseTransitSetTomorrow[0] != null) { + calendarEventSpec.id = id_tomorrow; + calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow[0].getTimeInMillis() / 1000); + GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec); + } + + calendarEventSpec.type = CalendarEventSpec.TYPE_SUNSET; + calendarEventSpec.title = "Sunset"; + if (sunriseTransitSetTomorrow[2] != null) { + calendarEventSpec.id = id_tomorrow; + calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow[2].getTimeInMillis() / 1000); + GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java index 5282e7a5..1fa0364f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java @@ -14,7 +14,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; public class BluetoothConnectReceiver extends BroadcastReceiver { - private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class); + private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class); final DeviceCommunicationService service; @@ -32,7 +32,7 @@ public class BluetoothConnectReceiver extends BroadcastReceiver { GBDevice gbDevice = service.getGBDevice(); if (gbDevice != null) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device.getAddress().equals(gbDevice.getAddress())) { + if (device.getAddress().equals(gbDevice.getAddress()) && gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { LOG.info("will connect to " + gbDevice.getName()); GBApplication.deviceService().connect(); } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index d910e75e..67c7e8e7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -3,6 +3,7 @@ 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; @@ -18,20 +19,13 @@ public class MusicPlaybackReceiver extends BroadcastReceiver { String artist = intent.getStringExtra("artist"); String album = intent.getStringExtra("album"); String track = intent.getStringExtra("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")); - } - */ + LOG.info("Current track: " + artist + ", " + album + ", " + track); MusicSpec musicSpec = new MusicSpec(); musicSpec.artist = artist; - musicSpec.artist = album; - musicSpec.artist = track; + musicSpec.album = album; + musicSpec.track = track; GBApplication.deviceService().onSetMusicInfo(musicSpec); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 1e46d978..25275534 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -42,6 +42,7 @@ public class GBDevice implements Parcelable { public static final String EXTRA_DEVICE = "device"; private static final String DEVINFO_HW_VER = "HW: "; private static final String DEVINFO_FW_VER = "FW: "; + private static final String DEVINFO_ADDR = "ADDR: "; private final String mName; private final String mAddress; private final DeviceType mDeviceType; @@ -346,6 +347,9 @@ public class GBDevice implements Parcelable { if (mFirmwareVersion != null) { result.add(new GenericItem(DEVINFO_FW_VER, mFirmwareVersion)); } + if (mAddress != null) { + result.add(new GenericItem(DEVINFO_ADDR, mAddress)); + } Collections.sort(result); return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 61af3fcb..2e598eac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; @@ -223,4 +224,24 @@ public class GBDeviceService implements DeviceService { .putExtra(EXTRA_BOOLEAN_ENABLE, enable); invokeService(intent); } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + Intent intent = createIntent().setAction(ACTION_ADD_CALENDAREVENT) + .putExtra(EXTRA_CALENDAREVENT_ID, calendarEventSpec.id) + .putExtra(EXTRA_CALENDAREVENT_TYPE, calendarEventSpec.type) + .putExtra(EXTRA_CALENDAREVENT_TIMESTAMP, calendarEventSpec.timestamp) + .putExtra(EXTRA_CALENDAREVENT_DURATION, calendarEventSpec.durationInSeconds) + .putExtra(EXTRA_CALENDAREVENT_TITLE, calendarEventSpec.title) + .putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description); + invokeService(intent); + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + Intent intent = createIntent().setAction(ACTION_DELETE_CALENDAREVENT) + .putExtra(EXTRA_CALENDAREVENT_TYPE, type) + .putExtra(EXTRA_CALENDAREVENT_ID, id); + invokeService(intent); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java new file mode 100644 index 00000000..438123e4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CalendarEventSpec.java @@ -0,0 +1,14 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class CalendarEventSpec { + public static final byte TYPE_UNKNOWN = 0; + public static final byte TYPE_SUNRISE = 1; + public static final byte TYPE_SUNSET = 2; + + public byte type; + public long id; + public int timestamp; + public int durationInSeconds; + public String title; + public String description; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 2cf979cd..aaf02798 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -36,6 +36,8 @@ public interface DeviceService extends EventHandler { String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement"; String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support"; String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement"; + String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent"; + String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent"; String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_NOTIFICATION_BODY = "notification_body"; String EXTRA_NOTIFICATION_FLAGS = "notification_flags"; @@ -65,6 +67,12 @@ public interface DeviceService extends EventHandler { String EXTRA_REALTIME_STEPS = "realtime_steps"; String EXTRA_TIMESTAMP = "timestamp"; String EXTRA_HEART_RATE_VALUE = "hr_value"; + String EXTRA_CALENDAREVENT_ID = "calendarevent_id"; + String EXTRA_CALENDAREVENT_TYPE = "calendarevent_type"; + String EXTRA_CALENDAREVENT_TIMESTAMP = "calendarevent_timestamp"; + String EXTRA_CALENDAREVENT_DURATION = "calendarevent_duration"; + String EXTRA_CALENDAREVENT_TITLE = "calendarevent_title"; + String EXTRA_CALENDAREVENT_DESCRIPTION = "calendarevent_description"; void start(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 37d217f8..fdafa526 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -25,6 +25,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.K9Receiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver; @@ -34,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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.NotificationSpec; @@ -43,10 +45,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; 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_CALLSTATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_CALENDAREVENT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT; @@ -70,6 +74,12 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DESCRIPTION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DURATION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_ID; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TIMESTAMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS; @@ -108,6 +118,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private MusicPlaybackReceiver mMusicPlaybackReceiver = null; private TimeChangeReceiver mTimeChangeReceiver = null; private BluetoothConnectReceiver mBlueToothConnectReceiver = null; + private AlarmReceiver mAlarmReceiver = null; private Random mRandom = new Random(); @@ -268,6 +279,23 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mDeviceSupport.onNotification(notificationSpec); break; } + case ACTION_ADD_CALENDAREVENT: { + CalendarEventSpec calendarEventSpec = new CalendarEventSpec(); + calendarEventSpec.id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1); + calendarEventSpec.type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1); + calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1); + calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1); + calendarEventSpec.title = intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE); + calendarEventSpec.description = intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION); + mDeviceSupport.onAddCalendarEvent(calendarEventSpec); + break; + } + case ACTION_DELETE_CALENDAREVENT: { + long id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1); + byte type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1); + mDeviceSupport.onDeleteCalendarEvent(type, id); + break; + } case ACTION_REBOOT: { mDeviceSupport.onReboot(); break; @@ -469,6 +497,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mBlueToothConnectReceiver = new BluetoothConnectReceiver(this); registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED)); } + if (mAlarmReceiver == null) { + mAlarmReceiver = new AlarmReceiver(); + registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM")); + } } else { if (mPhoneCallReceiver != null) { unregisterReceiver(mPhoneCallReceiver); @@ -498,6 +530,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mBlueToothConnectReceiver); mBlueToothConnectReceiver = null; } + if (mAlarmReceiver != null) { + unregisterReceiver(mAlarmReceiver); + mAlarmReceiver = null; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index 8047aad6..019722b5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -13,6 +13,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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.NotificationSpec; @@ -268,4 +269,20 @@ public class ServiceDeviceSupport implements DeviceSupport { } delegate.onEnableRealtimeHeartRateMeasurement(enable); } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + if (checkBusy("add calendar event")) { + return; + } + delegate.onAddCalendarEvent(calendarEventSpec); + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + if (checkBusy("delete calendar event")) { + return; + } + delegate.onDeleteCalendarEvent(type, id); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java index 04179458..26118112 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java @@ -147,7 +147,6 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im } private void gattServicesDiscovered(List discoveredGattServices) { - if (discoveredGattServices == null) { return; } @@ -180,7 +179,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im @Override public void onServicesDiscovered(BluetoothGatt gatt) { - gattServicesDiscovered(getQueue().getSupportedGattServices()); + gattServicesDiscovered(gatt.getServices()); initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue()); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index f6009435..fc7358a8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -156,11 +156,9 @@ public final class BtLEQueue { LOG.info("Attempting to connect to " + mGbDevice.getName()); mBluetoothAdapter.cancelDiscovery(); BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress()); -// boolean result; synchronized (mGattMonitor) { // connectGatt with true doesn't really work ;( too often connection problems mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback); -// result = mBluetoothGatt.connect(); } boolean result = mBluetoothGatt != null; if (result) { @@ -223,7 +221,11 @@ public final class BtLEQueue { private boolean maybeReconnect() { if (mAutoReconnect && mBluetoothGatt != null) { LOG.info("Enabling automatic ble reconnect..."); - return mBluetoothGatt.connect(); + boolean result = mBluetoothGatt.connect(); + if (result) { + setDeviceConnectionState(State.WAITING_FOR_RECONNECT); + } + return result; } return false; } @@ -308,6 +310,12 @@ public final class BtLEQueue { public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { LOG.debug("connection state change, newState: " + newState + getStatusString(status)); + synchronized (mGattMonitor) { + if (mBluetoothGatt == null) { + mBluetoothGatt = gatt; + } + } + if (!checkCorrectGattInstance(gatt, "connection state event")) { return; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java index 3ba149df..c1c36258 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java @@ -83,7 +83,7 @@ public class DeviceInfo extends AbstractInfo { } public boolean supportsHeartrate() { - return isMili1S() || (test1AHRMode && isMili1A()); + return isMiliPro() || isMili1S() || (test1AHRMode && isMili1A()); } @Override @@ -116,6 +116,10 @@ public class DeviceInfo extends AbstractInfo { return hwVersion == 6; } + public boolean isMiliPro() { + return hwVersion == 8 || (feature == 8 && appearance == 0); + } + public String getHwVersion() { if (isMili1()) { return MiBandConst.MI_1; @@ -129,6 +133,9 @@ public class DeviceInfo extends AbstractInfo { if (isAmazFit()) { return MiBandConst.MI_AMAZFIT; } + if (isMiliPro()) { + return MiBandConst.MI_PRO; + } return "?"; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 2d97818d..fd00d02a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; @@ -360,10 +361,18 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { LOG.info("Attempting to set wear location..."); BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT); if (characteristic != null) { - int location = MiBandCoordinator.getWearLocation(getDevice().getAddress()); - transaction.write(characteristic, new byte[]{ - MiBandService.COMMAND_SET_WEAR_LOCATION, - (byte) location + transaction.add(new ConditionalWriteAction(characteristic) { + @Override + protected byte[] checkCondition() { + if (getDeviceInfo() != null && getDeviceInfo().isAmazFit()) { + return null; + } + int location = MiBandCoordinator.getWearLocation(getDevice().getAddress()); + return new byte[]{ + MiBandService.COMMAND_SET_WEAR_LOCATION, + (byte) location + }; + } }); } else { LOG.info("Unable to set Wear Location"); @@ -382,6 +391,16 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + // not supported + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + // not supported + } + /** * Part of device initialization process. Do not call manually. * @@ -834,6 +853,9 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { private void handleHeartrate(byte[] value) { if (value.length == 2 && value[0] == 6) { int hrValue = (value[1] & 0xff); + if (LOG.isDebugEnabled()) { + LOG.debug("heart rate: " + hrValue); + } Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT) .putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue) .putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java index 26d55f73..710178f9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandler.java @@ -17,6 +17,10 @@ public class AppMessageHandler { mPebbleProtocol = pebbleProtocol; } + public boolean isEnabled() { + return true; + } + public UUID getUUID() { return mUUID; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java index daa0b020..1a7e4b97 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java @@ -20,6 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider; import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class AppMessageHandlerMisfit extends AppMessageHandler { @@ -41,6 +42,12 @@ public class AppMessageHandlerMisfit extends AppMessageHandler { private final MisfitSampleProvider sampleProvider = new MisfitSampleProvider(); + @Override + public boolean isEnabled() { + Prefs prefs = GBApplication.getPrefs(); + return prefs.getBoolean("pebble_sync_misfit", true); + } + @Override public GBDeviceEvent[] handleMessage(ArrayList> pairs) { for (Pair pair : pairs) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java index 3c6517d6..62f4d39a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java @@ -18,6 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepMonitorResult; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MorpheuzSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class AppMessageHandlerMorpheuz extends AppMessageHandler { @@ -56,6 +57,12 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler { return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); } + @Override + public boolean isEnabled() { + Prefs prefs = GBApplication.getPrefs(); + return prefs.getBoolean("pebble_sync_morpheuz", true); + } + @Override public GBDeviceEvent[] handleMessage(ArrayList> pairs) { int ctrl_message = 0; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java index b7d42544..35d84be2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java @@ -13,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.util.GB; -class DatalogSessionHealthOverlayData extends DatalogSession { +class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class); @@ -26,6 +26,10 @@ class DatalogSessionHealthOverlayData extends DatalogSession { public boolean handleMessage(ByteBuffer datalogMessage, int length) { LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); + if (!isPebbleHealthEnabled()) { + return false; + } + int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java index 2162f9d5..455e6459 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java @@ -13,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.util.GB; -class DatalogSessionHealthSleep extends DatalogSession { +class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class); @@ -25,6 +25,11 @@ class DatalogSessionHealthSleep extends DatalogSession { @Override public boolean handleMessage(ByteBuffer datalogMessage, int length) { LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); + + if (!isPebbleHealthEnabled()) { + return false; + } + int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java index f89f0363..06151d7a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java @@ -16,7 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class DatalogSessionHealthSteps extends DatalogSession { +public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class); @@ -29,6 +29,10 @@ public class DatalogSessionHealthSteps extends DatalogSession { public boolean handleMessage(ByteBuffer datalogMessage, int length) { LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length)); + if (!isPebbleHealthEnabled()) { + return false; + } + int timestamp; byte recordLength, recordNum; short recordVersion; //probably diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java new file mode 100644 index 00000000..6df7a751 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java @@ -0,0 +1,18 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +abstract class DatalogSessionPebbleHealth extends DatalogSession { + + DatalogSessionPebbleHealth(byte id, UUID uuid, int tag, byte itemType, short itemSize) { + super(id, uuid, tag, itemType, itemSize); + } + + protected boolean isPebbleHealthEnabled() { + Prefs prefs = GBApplication.getPrefs(); + return prefs.getBoolean("pebble_sync_health", true); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index a14fb496..fcc19641 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -161,6 +161,8 @@ public class PebbleIoThread extends GBDeviceIoThread { @Override protected boolean connect(String btDeviceAddress) { GBDevice.State originalState = gbDevice.getState(); + gbDevice.setState(GBDevice.State.CONNECTING); + gbDevice.sendDeviceUpdateIntent(getContext()); try { // contains only one ":"? then it is addr:port int firstColon = btDeviceAddress.indexOf(":"); @@ -188,6 +190,8 @@ public class PebbleIoThread extends GBDeviceIoThread { } catch (IOException e) { e.printStackTrace(); gbDevice.setState(originalState); + gbDevice.sendDeviceUpdateIntent(getContext()); + mInStream = null; mOutStream = null; mBtSocket = null; @@ -197,12 +201,8 @@ public class PebbleIoThread extends GBDeviceIoThread { mPebbleProtocol.setForceProtocol(prefs.getBoolean("pebble_force_protocol", false)); mIsConnected = true; - if (originalState == GBDevice.State.WAITING_FOR_RECONNECT) { - gbDevice.setState(GBDevice.State.INITIALIZED); - } else { - gbDevice.setState(GBDevice.State.CONNECTED); - write(mPebbleProtocol.encodeFirmwareVersionReq()); - } + write(mPebbleProtocol.encodeFirmwareVersionReq()); + gbDevice.setState(GBDevice.State.CONNECTED); gbDevice.sendDeviceUpdateIntent(getContext()); return true; @@ -210,15 +210,18 @@ public class PebbleIoThread extends GBDeviceIoThread { @Override public void run() { - gbDevice.setState(GBDevice.State.CONNECTING); - gbDevice.sendDeviceUpdateIntent(getContext()); - mIsConnected = connect(gbDevice.getAddress()); - enablePebbleKitReceiver(mIsConnected); - mQuit = !mIsConnected; // quit if not connected + if (!mIsConnected) { + if (GBApplication.getGBPrefs().getAutoReconnect()) { + gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); + gbDevice.sendDeviceUpdateIntent(getContext()); + } + return; + } byte[] buffer = new byte[8192]; - + enablePebbleKitReceiver(true); + mQuit = false; while (!mQuit) { try { if (mIsInstalling) { @@ -361,8 +364,9 @@ public class PebbleIoThread extends GBDeviceIoThread { mIsConnected = false; int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10); if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) { - gbDevice.setState(GBDevice.State.CONNECTING); + gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); gbDevice.sendDeviceUpdateIntent(getContext()); + int delaySeconds = 1; while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) { LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 52adb045..d53de9b1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; 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.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; @@ -240,6 +241,8 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte LENGTH_UUID = 16; + static final long GB_UUID_MASK = 0x4767744272646700L; + // base is -5 private static final String[] hwRevisions = { // Emulator @@ -453,7 +456,6 @@ public class PebbleProtocol extends GBDeviceProtocol { if (isFw3x) { // 3.x notification - //return encodeTimelinePin(id, (int) ((ts + 600) & 0xffffffffL), (short) 90, PebbleIconID.TIMELINE_CALENDAR, title); // really, this is just for testing return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.cannedReplies); } else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) { // 2.x notification @@ -466,6 +468,29 @@ public class PebbleProtocol extends GBDeviceProtocol { } } + @Override + public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong(); + int iconId; + switch (calendarEventSpec.type) { + case CalendarEventSpec.TYPE_SUNRISE: + iconId = PebbleIconID.SUNRISE; + break; + case CalendarEventSpec.TYPE_SUNSET: + iconId = PebbleIconID.SUNSET; + break; + default: + iconId = PebbleIconID.TIMELINE_CALENDAR; + } + + return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) calendarEventSpec.durationInSeconds, iconId, calendarEventSpec.title, calendarEventSpec.description); + } + + @Override + public byte[] encodeDeleteCalendarEvent(byte type, long id) { + return encodeBlobdb(new UUID(GB_UUID_MASK | type, id), BLOBDB_DELETE, BLOBDB_PIN, null); + } + @Override public byte[] encodeSetTime() { long ts = System.currentTimeMillis(); @@ -744,11 +769,10 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - private byte[] encodeTimelinePin(int id, int timestamp, short duration, int icon_id, String title, String subtitle) { + private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, String title, String subtitle) { final short TIMELINE_PIN_LENGTH = 46; icon_id |= 0x80000000; - UUID uuid = new UUID(mRandom.nextLong(), ((long) mRandom.nextInt() << 32) | id); byte attributes_count = 2; byte actions_count = 0; @@ -1236,10 +1260,10 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put(PHONEVERSION_APPVERSION_MAGIC); buf.put((byte) 3); // major - buf.put((byte) 8); // minor - buf.put((byte) 1); // patch + buf.put((byte) 12); // minor + buf.put((byte) 0); // patch buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putLong(0x00000000000000af); //flags + buf.putLong(0x00000000000001af); //flags return buf.array(); } @@ -1804,7 +1828,7 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info(ENDPOINT_NAME + ": (cmd:" + command + ")" + uuid); break; } - return null; + return new GBDeviceEvent[]{null}; } private GBDeviceEvent decodeBlobDb(ByteBuffer buf) { @@ -2094,8 +2118,12 @@ public class PebbleProtocol extends GBDeviceProtocol { AppMessageHandler handler = mAppMessageHandlers.get(uuid); if (handler != null) { - ArrayList> dict = decodeDict(buf); - devEvts = handler.handleMessage(dict); + if (handler.isEnabled()) { + ArrayList> dict = decodeDict(buf); + devEvts = handler.handleMessage(dict); + } else { + devEvts = new GBDeviceEvent[]{null}; + } } else { try { devEvts = decodeDictToJSONAppMessage(uuid, buf); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index e396d03f..b0bc3b47 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -12,6 +12,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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.NotificationSpec; @@ -119,4 +120,18 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { public void onSetAlarms(ArrayList alarms) { //nothing to do ATM } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + if (reconnect()) { + super.onAddCalendarEvent(calendarEventSpec); + } + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + if (reconnect()) { + super.onDeleteCalendarEvent(type, id); + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index 5dfa8b2a..78e15667 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -8,6 +8,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; 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.NotificationSpec; @@ -187,4 +188,16 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable); sendToDevice(bytes); } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + byte[] bytes = gbDeviceProtocol.encodeAddCalendarEvent(calendarEventSpec); + sendToDevice(bytes); + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + byte[] bytes = gbDeviceProtocol.encodeDeleteCalendarEvent(type, id); + sendToDevice(bytes); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index bdf1b101..a226c094 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.serial; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; public abstract class GBDeviceProtocol { @@ -65,6 +66,14 @@ public abstract class GBDeviceProtocol { public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; } + public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + return null; + } + + public byte[] encodeDeleteCalendarEvent(byte type, long id) { + return null; + } + public GBDeviceEvent[] decodeResponse(byte[] responseData) { return null; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java index b354edb7..470ef392 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java @@ -182,4 +182,31 @@ public class FileUtils { } return out.toByteArray(); } + + public static boolean deleteRecursively(File dir) { + if (!dir.exists()) { + return true; + } + if (dir.isFile()) { + return dir.delete(); + } + for (File sub : dir.listFiles()) { + if (!deleteRecursively(sub)) { + return false; + } + } + return dir.delete(); + } + + public static File createTempDir(String prefix) throws IOException { + File parent = new File(System.getProperty("java.io.tmpdir", "/tmp")); + for (int i = 1; i < 100; i++) { + String name = prefix + (int) (Math.random() * 100000); + File dir = new File(parent, name); + if (dir.mkdirs()) { + return dir; + } + } + throw new IOException("Cannot create temporary directory in " + parent); + } } \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1588000a..9d337f73 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -28,6 +28,7 @@ Einstellungen Allgemeine Einstellungen Verbinde, wenn Bluetooth eingeschaltet wird + Verbindungen automatisch wiederherstellen Bevorzugter Audioplayer Standard Datum und Zeit @@ -59,6 +60,12 @@ Bevorzugter Aktivitätstracker Erlaube Zugriff von anderen Android Apps Experimentelle Unterstützung für Android Apps, die PebbleKit benutzen + Sonnenauf- und -untergang + Sende Sonnenauf- und -untergangszeiten abhänging vom Standort auf die Pebble Timeline + Standort + Standort Bestimmen + Breitengrad + Längengrad Benachrichtigungsprotokoll erzwingen Diese Option erzwingt das neuste Benachrichtigungsprotokoll abhängig von der Firmwareversion. NUR EINSCHALTEN, WENN DU WEISST WAS DU TUST! Ungetestete Features freischalten @@ -231,5 +238,4 @@ Firmware wurde nicht gesendet Herzfrequenz Herzfrequenz - Verbindungen automatisch wiederherstellen diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3f1e3135..6ca7ee3b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -14,6 +14,7 @@ Gestione app Cancella + Cancella e rimuovi dalla cache Blocco notifiche @@ -27,11 +28,15 @@ Impostazioni Impostazioni globali Collegati al dispositivo quando il bluetooth viene acceso + Riconessione automatica Applicazione musicale preferita Default Data e ora Sincronizza l\'ora Sincronizza l\'orario al collegamento oppure quando viene cambiata l\'ora / il fuso orario in android. + Tema + Chiaro + Scuro Notifiche Ripetizioni Chiamate telefoniche @@ -41,6 +46,8 @@ Supporto per applicazioni che inviano le notifiche a Pebble usando Intents. Può essere usato per Conversations. Notifiche generiche … anche se lo schermo è acceso + Non disturbare + Non inviare notifiche nei periodi configurati come \"non disturbare\" sempre se lo schermo è spento mai @@ -53,6 +60,12 @@ Tracker delle attività preferito Consenti accesso ad altre applicazioni Attiva l\'accesso sperimentale ad applicazioni Android che usano PebbleKit + Alba e tramonto + Mostra gli orari calcolati per l\'alba e il tramonto sulla timeline + Posizione + Acquisisci posizione + Latitudin + Longitudin Forza protocollo delle notifiche Questa opzione forza l\'utilizzo della versione più recente delle notifiche in dipendenza del firmware del tuo dispositivo. ABILITALO SOLO SE SAI COSA STAI FACENDO! Abilita funzionalità non testate @@ -70,6 +83,9 @@ Notifica di prova creata da Gadgetbridge Bluetooth non supportato. Bluetooth disabilitato. + tocca il dispositivo connesso per gestire le App + tocca il dispositivo connesso per visualizzare l\'attività + tocca il dispositivo a cui connettersi Impossibile connettersi. Indirizzo BT non valido? Gadgetbridge in esecuzione installazione del binario %1$d/%2$d @@ -100,11 +116,13 @@ Dati dell\'utente non inseriti, vengono usati dati d\'esempio. Quando la Mi Band vibra e lampeggia, dalle qualche leggero colpetto. Installa + Imposta il tuo dispositivo perchè sia rilevabile. I dispositivi attualmente connessi non saranno probabilmente rilevati. Se non vedi il tuo dispositivo entro un paio di minuti, riprova dopo avere riavviato il dispositivo Android. Nota: Immagine dispositivo Nome / Soprannome Numero vibrazioni Monitoraggio del sonno + Salva il log su file inizializzazione in corso Recupero dati attività Da %1$s a %2$s @@ -178,6 +196,8 @@ Non confermare il trasferimento dati Se il trasferimento non viene confermato, i dati rimangono memorizzati sulla Mi Band. Utile se GB è usato insieme ad altre app. Conserva i dati delle attività sulla Mi Band anche dopo averli sincronizzati. Utile se GB è usato insieme ad altre app. + Utilizza la modalità a bassa latenza per gli aggiornamenti del firmware + Può essere utile quando l\'aggiornamento del firmware fallisce Storico dei passi Passi/minuto Passi totali @@ -193,6 +213,7 @@ Firmware non compatibile Questo firmware non è compatibile con il dispositivo Sveglie da riservare per i prossimi eventi del calendario + Utilizza il sensore del battito cardiaco per migliorare il riconoscimento del sonno in attesa di riconessione Re-installazion Informazioni sull\'utilizzatore @@ -211,4 +232,11 @@ Impostata sveglia per %1$02d:%2$02d HW: %1$s FW: %1$s + Errore durante la creazione della directory per i file di log: %1$s + HR: + Aggiornamento firmware in corso + Firmware non inviato + Battito cardiaco + Battito cardiaco + Offset orologio del dispositivo in ore (per l\'identificazione del sonno dei lavoratori a turni) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f4013879..ccdfc41a 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -28,6 +28,7 @@ 設定 一般設定 Bluetoothがオンになったときにデバイスに接続 + 自動的に再接続 お好みのオーディオプレイヤー デフォルト 日付と時刻 @@ -59,6 +60,12 @@ お好みのアクティビティ トラッカー 第三者のアンドロイドアップにアクセス権利を与える PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします + 日の出と日の入り + 場所に基づいて、Pebble のタイムラインに日の出・日の入りの時間を送ります + 場所 + 場所の取得 + 緯度 + 経度 通知プロトコルを強制する このオプションを指定すると、ファームウェアのバージョンに応じて強制的に最新の通知プロトコルを使用します。何をしているかわかっている場合のみ有効にしてください! 未テストの機能を有効にする @@ -231,5 +238,4 @@ ファームウェアを送信しませんでした 心拍数 心拍数 - 自動的に再接続 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2afb4a02..b3735145 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -27,6 +27,7 @@ 설정 일반 설정 블루투스가 켜지면 기기에 접속하기 + 자동으로 재연결 선호하는 오디오 플레이어 기본값 날짜와 시간 @@ -224,5 +225,4 @@ 펌웨어가 전송되지 않음 심박수 심박수 - 자동으로 재연결 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 937c29f2..8cf1b287 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -11,6 +11,36 @@ light dark + + System Default + Deutsch + English + Español + Français + Polski + Русский + Tiếng Việt + Türkçe + Українська + 한국어 + 日本語 + + + + default + de + en + es + fr + pl + ru + vi + tr + uk + ko + ja + + @string/always @string/when_screen_off diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 141484d1..35c8c70f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,15 +35,20 @@ General Settings Connect to device when Bluetooth turned on + Reconnect automatically Preferred Audioplayer Default + Date and Time Sync time Sync time to device when connecting and when time or timezone changes on Android + Theme Light Dark + Language + Notifications Repetitions Phone Calls @@ -69,14 +74,30 @@ Mi Band address Pebble Settings + + Activity Trackers Preferred Activitytracker + Sync Pebble Health + Sync Misfit + Sync Morpheuz + Allow 3rd Party Android App Access Enable experimental support for Android Apps using PebbleKit + + Sunrise and Sunset + Send sunrise and sunset times based on the location to the pebble timeline + + Location + Acquire Location + Latitude + Longitude + Force Notification Protocol This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING! Enable untested features Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING! Reconnection Attempts + not connected connecting connected @@ -224,6 +245,7 @@ This firmware is not compatible with the device Alarms to reserve for upcoming events Use Heartrate Sensor to improve sleep detection + Device time offset in hours (for detecting sleep of shift workers) waiting for reconnect Reinstall @@ -251,5 +273,4 @@ Firmware not sent Heart Rate Heart Rate - Reconnect automatically diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index abe77eba..a4918548 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,13 @@ + + Pebble: option to send sunrise and sunset events to timeline + Pebble: fix problems with unknown app keys while configuring watchfaces + Mi Band: BLE connection fixes + Fixes for enabling logging at whithout restarting Gadgetbridge + Re-enable device paring activity on Android 6 (BLE scanning needs the location preference) + Display device address in device info + Pebble: fix more reconnnect issues Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health @@ -7,6 +15,7 @@ Pebble: enable pbw cache and watchface configuration for Firmware 2.x Pebble: allow enabling of Pebble Health without "untested features" being enabled Honour "Do Not Disturb" for phone calls and SMS + Pebble: fix music information being messed up Pebble: hopefully fix some reconnect issues diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml index 37e5d777..54888426 100644 --- a/app/src/main/res/xml/miband_preferences.xml +++ b/app/src/main/res/xml/miband_preferences.xml @@ -35,6 +35,14 @@ android:defaultValue="false" android:key="mi_hr_sleep_detection" android:title="@string/miband_prefs_hr_sleep_detection" /> + + + + + + + + android:summary="%s" + android:title="@string/pref_title_pebble_activitytracker" /> + + > + + + + + + 5) { + File dir = new File(dirName); + return dir; + } + fail("Property " + Logging.PROP_LOGFILES_DIR + " has invalid value: " + dirName); + return null; // not reached + } + + @Test + public void testToggleLogging() { + try { + File dir = getLogFilesDir(); + } catch (AssertionFailedError ignored) { + // expected, as not yet set up + } + + try { + logging.setupLogging(true); + File dir = getLogFilesDir(); + assertEquals(1, dir.list().length); + assertNotNull(logging.getFileLogger()); + assertTrue(logging.getFileLogger().isStarted()); + + logging.setupLogging(false); + assertNotNull(logging.getFileLogger()); + assertFalse(logging.getFileLogger().isStarted()); + + logging.setupLogging(true); + assertNotNull(logging.getFileLogger()); + assertTrue(logging.getFileLogger().isStarted()); + } catch (AssertionFailedError ex) { + logging.debugLoggingConfiguration(); + System.err.println(System.getProperty("java.class.path")); + throw ex; + } + } +}