diff --git a/CHANGELOG.md b/CHANGELOG.md index bf801f266..c34cdbdc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,21 @@ ###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 * Pebble: hopefully fix some reconnect issues * Mi Band: fix live activity monitoring running forever if back button pressed diff --git a/README.md b/README.md index 9367cd440..6f888ce8c 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 a2f25e273..82f0d9a12 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.7" - versionCode 51 + 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' compile 'com.github.freeyourgadget:greendao:c3830951e5dd3d1e63d7bac600d5f773b81df363' } @@ -105,7 +110,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 5d0ac7d12..c72495ef2 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; private static DBHandler lockHandler; + /** + * Note: is null on Lollipop and Kitkat + */ private static NotificationManager notificationManager; 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) { @@ -113,11 +117,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); setupDatabase(this); @@ -131,7 +130,7 @@ public class GBApplication extends Application { LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); if (isRunningMarshmallowOrLater()) { - notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE); + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } // for testing DB stuff @@ -139,6 +138,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); @@ -148,71 +151,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); - } - static void setupDatabase(Context context) { // DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test-db", null); DBOpenHelper helper = new DBOpenHelper(context, "test-db", null); @@ -285,7 +223,7 @@ public class GBApplication extends Application { boolean exists = false; int starred = 0; try { - if (cursor.moveToFirst()) { + if (cursor != null && cursor.moveToFirst()) { exists = true; starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED)); } 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 000000000..7ea3c8e90 --- /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/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index 2e6bbba04..f66bd7310 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -47,7 +48,6 @@ public class AppManagerActivity extends GBActivity { if (action.equals(GBApplication.ACTION_QUIT)) { finish(); } else if (action.equals(ACTION_REFRESH_APPLIST)) { - appList.clear(); int appCount = intent.getIntExtra("app_count", 0); for (Integer i = 0; i < appCount; i++) { String appName = intent.getStringExtra("app_name" + i.toString()); @@ -55,11 +55,21 @@ public class AppManagerActivity extends GBActivity { UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString())); GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)]; - appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType)); - } - - if (prefs.getBoolean("pebble_force_untested", false)) { - appList.addAll(getSystemApps()); + boolean found = false; + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + app.setOnDevice(true); + iter.set(app); + found = true; + break; + } + } + if (!found) { + GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType); + app.setOnDevice(true); + appList.add(app); + } } mGBDeviceAppAdapter.notifyDataSetChanged(); @@ -76,8 +86,10 @@ public class AppManagerActivity extends GBActivity { private List getSystemApps() { List systemApps = new ArrayList<>(); - systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); - systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + if (prefs.getBoolean("pebble_force_untested", false)) { + systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + } if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); } @@ -149,9 +161,7 @@ public class AppManagerActivity extends GBActivity { appList.addAll(getCachedApps()); - if (prefs.getBoolean("pebble_force_untested", false)) { - appList.addAll(getSystemApps()); - } + appList.addAll(getSystemApps()); IntentFilter filter = new IntentFilter(); filter.addAction(GBApplication.ACTION_QUIT); @@ -171,11 +181,13 @@ public class AppManagerActivity extends GBActivity { if (!selectedApp.isInCache()) { menu.removeItem(R.id.appmanager_app_reinstall); + menu.removeItem(R.id.appmanager_app_delete_cache); } if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { menu.removeItem(R.id.appmanager_health_activate); menu.removeItem(R.id.appmanager_health_deactivate); - } else if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { + } + if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) { menu.removeItem(R.id.appmanager_app_delete); } if (!selectedApp.isConfigurable()) { @@ -184,10 +196,42 @@ public class AppManagerActivity extends GBActivity { menu.setHeaderTitle(selectedApp.getName()); } + private void removeAppFromList(UUID uuid) { + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + iter.remove(); + mGBDeviceAppAdapter.notifyDataSetChanged(); + break; + } + } + } + @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.appmanager_health_deactivate: + case R.id.appmanager_app_delete_cache: + String baseName; + try { + baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); + } catch (IOException e) { + LOG.warn("could not get external dir while trying to access pbw cache."); + return true; + } + + String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"}; + + for (String suffix : suffixToDelete) { + File fileToDelete = new File(baseName + suffix); + if (!fileToDelete.delete()) { + LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString()); + } else { + LOG.info("deleted file: " + fileToDelete.toString()); + } + } + removeAppFromList(selectedApp.getUUID()); + // fall through case R.id.appmanager_app_delete: GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); return true; @@ -196,7 +240,7 @@ public class AppManagerActivity extends GBActivity { try { cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); } catch (IOException e) { - LOG.warn("could not get external dir while reading pbw cache."); + LOG.warn("could not get external dir while trying to access pbw cache."); return true; } GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath)); 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 702aa1346..37de8193f 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 b1d2b7428..f53cf085e 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 f2ab531b6..3f00ced47 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 7dba008be..a5181dccc 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 38e055647..dd9c2599a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/GBActivity.java @@ -1,15 +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.entities.DaoSession; +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()) { @@ -18,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 c6479695e..41d6bc556 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/adapter/GBDeviceAppAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java index 5a8137926..d4ae263f3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java @@ -41,7 +41,15 @@ public class GBDeviceAppAdapter extends ArrayAdapter { ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image); deviceAppVersionAuthorLabel.setText(getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator())); - deviceAppNameLabel.setText(deviceApp.getName()); + + // FIXME: replace with small icons + String appNameLabelText = deviceApp.getName(); + if (deviceApp.isInCache() || deviceApp.isOnDevice()) { + appNameLabelText += " (" + (deviceApp.isInCache() ? "C" : "") + + (deviceApp.isOnDevice() ? "D" : "") + ")"; + } + deviceAppNameLabel.setText(appNameLabelText); + switch (deviceApp.getType()) { case APP_GENERIC: deviceImageView.setImageResource(R.drawable.ic_watchapp); 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 a6dd21bf1..602ca5c85 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 8379d75bd..ca8b9e64e 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 0de8e05ed..bffb2f278 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 @@ -144,6 +144,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 88a5e230c..e41cf9be8 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 e49ae1abb..7fb29ce96 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/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java index 30becd117..9d66c0dbc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java @@ -129,10 +129,6 @@ public class PBWInstallHandler implements InstallHandler { return; } - if (!device.getFirmwareVersion().startsWith("v3")) { - return; - } - File destDir; GBDeviceApp app = mPBWReader.getGBDeviceApp(); try { 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 000000000..e9470d31a --- /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 new file mode 100644 index 000000000..1fa0364ff --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java @@ -0,0 +1,43 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; + +public class BluetoothConnectReceiver extends BroadcastReceiver { + + private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class); + + final DeviceCommunicationService service; + + public BluetoothConnectReceiver(DeviceCommunicationService service) { + this.service = service; + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (!action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + return; + } + LOG.info("got connection attempt"); + GBDevice gbDevice = service.getGBDevice(); + if (gbDevice != null) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device.getAddress().equals(gbDevice.getAddress()) && gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + LOG.info("will connect to " + gbDevice.getName()); + GBApplication.deviceService().connect(); + } else { + LOG.info("won't connect to " + device.getAddress() + "(" + device.getName() + ")"); + } + } + } +} 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 d910e75eb..67c7e8e70 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 8cc48c738..837d6b741 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; @@ -355,6 +356,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/GBDeviceApp.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java index d2280d177..f55876453 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java @@ -12,6 +12,7 @@ public class GBDeviceApp { private final UUID uuid; private final Type type; private final boolean inCache; + private boolean isOnDevice; private final boolean configurable; public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) { @@ -23,6 +24,7 @@ public class GBDeviceApp { //FIXME: do not assume this.inCache = false; this.configurable = false; + this.isOnDevice = false; } public GBDeviceApp(JSONObject json, boolean configurable) { @@ -52,10 +54,18 @@ public class GBDeviceApp { this.configurable = configurable; } + public void setOnDevice(boolean isOnDevice) { + this.isOnDevice = isOnDevice; + } + public boolean isInCache() { return inCache; } + public boolean isOnDevice() { + return isOnDevice; + } + public String getName() { return name; } 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 61af3fcb8..2e598eacf 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 000000000..438123e49 --- /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 2cf979cda..aaf02798b 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 7738f40c7..fdafa5265 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service; import android.app.NotificationManager; import android.app.Service; +import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,6 +25,8 @@ 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; import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver; @@ -32,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; @@ -41,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; @@ -68,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; @@ -105,6 +117,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private PebbleReceiver mPebbleReceiver = null; private MusicPlaybackReceiver mMusicPlaybackReceiver = null; private TimeChangeReceiver mTimeChangeReceiver = null; + private BluetoothConnectReceiver mBlueToothConnectReceiver = null; + private AlarmReceiver mAlarmReceiver = null; private Random mRandom = new Random(); @@ -265,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; @@ -279,6 +310,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere } case ACTION_DISCONNECT: { mDeviceSupport.dispose(); + if (mGBDevice != null && mGBDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + setReceiversEnableState(false); + mGBDevice.setState(GBDevice.State.NOT_CONNECTED); + mGBDevice.sendDeviceUpdateIntent(this); + } mDeviceSupport = null; break; } @@ -457,6 +493,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere filter.addAction("android.intent.action.TIMEZONE_CHANGED"); registerReceiver(mTimeChangeReceiver, filter); } + if (mBlueToothConnectReceiver == null) { + 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); @@ -482,6 +526,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mTimeChangeReceiver); mTimeChangeReceiver = null; } + if (mBlueToothConnectReceiver != null) { + unregisterReceiver(mBlueToothConnectReceiver); + mBlueToothConnectReceiver = null; + } + if (mAlarmReceiver != null) { + unregisterReceiver(mAlarmReceiver); + mAlarmReceiver = null; + } } } @@ -549,4 +601,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere public GBPrefs getGBPrefs() { return GBApplication.getGBPrefs(); } + + public GBDevice getGBDevice() { + return mGBDevice; + } } 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 8047aad60..019722b54 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 04179458c..261181128 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 f60094356..fc7358a81 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 3ba149df6..c1c36258b 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 2d97818d7..fd00d02ac 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 c829335b5..6f57458e5 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 @@ -23,6 +23,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 43bd4f450..65f28a5e4 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 @@ -18,11 +18,13 @@ 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; import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class AppMessageHandlerMisfit extends AppMessageHandler { @@ -42,6 +44,13 @@ public class AppMessageHandlerMisfit extends AppMessageHandler { super(uuid, pebbleProtocol); } + @Override + public boolean isEnabled() { + Prefs prefs = GBApplication.getPrefs(); + int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); + return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MISFIT); + } + @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 9fbb6d690..7bdc6a901 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 @@ -11,7 +11,6 @@ import java.util.TimeZone; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; @@ -21,7 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MorpheuzSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.User; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class AppMessageHandlerMorpheuz extends AppMessageHandler { @@ -60,6 +59,13 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler { return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); } + @Override + public boolean isEnabled() { + Prefs prefs = GBApplication.getPrefs(); + int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); + return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MORPHEUZ); + } + @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 new file mode 100644 index 000000000..35d84be23 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java @@ -0,0 +1,103 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth { + + private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class); + + public DatalogSessionHealthOverlayData(byte id, UUID uuid, int tag, byte item_type, short item_size) { + super(id, uuid, tag, item_type, item_size); + taginfo = "(health - overlay data " + tag + " )"; + } + + @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 + short recordType; //probably: 1=sleep, 2=deep sleep, 5=??run??ignored for now + + if (0 != (length % itemSize)) + return false;//malformed message? + + int recordCount = length / itemSize; + OverlayRecord[] overlayRecords = new OverlayRecord[recordCount]; + + for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { + beginOfRecordPosition = initialPosition + recordIdx * itemSize; + datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record + recordVersion = datalogMessage.getShort(); + if ((recordVersion != 1) && (recordVersion != 3)) + return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it + + datalogMessage.getShort();//throwaway, unknown + recordType = datalogMessage.getShort(); + + overlayRecords[recordIdx] = new OverlayRecord(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); + } + + return store(overlayRecords);//NACK if we cannot store the data yet, the watch will send the overlay records again. + } + + private boolean store(OverlayRecord[] overlayRecords) { + DBHandler dbHandler = null; + SampleProvider sampleProvider = new HealthSampleProvider(); + try { + dbHandler = GBApplication.acquireDB(); + int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); + for (OverlayRecord overlayRecord : overlayRecords) { + if (latestTimestamp < (overlayRecord.timestampStart + overlayRecord.durationSeconds)) + return false; + switch (overlayRecord.type) { + case 1: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + break; + case 2: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); + break; + default: + //TODO: other values refer to unknown activity types. + } + } + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } finally { + if (dbHandler != null) { + dbHandler.release(); + } + } + return true; + } + + private class OverlayRecord { + int type; //1=sleep, 2=deep sleep + int offsetUTC; //probably + int timestampStart; + int durationSeconds; + + public OverlayRecord(int type, int offsetUTC, int timestampStart, int durationSeconds) { + this.type = type; + this.offsetUTC = offsetUTC; + this.timestampStart = timestampStart; + this.durationSeconds = durationSeconds; + } + } +} \ No newline at end of file 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 9d15d31f4..21e09b96d 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 @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; -import android.widget.Toast; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,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); @@ -27,65 +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)); - switch (this.tag) { - case 83: - return handleMessage83(datalogMessage, length); - case 84: - return handleMessage84(datalogMessage, length); - default: - return false; - } - } - private boolean handleMessage84(ByteBuffer datalogMessage, int length) { - int initialPosition = datalogMessage.position(); - int beginOfRecordPosition; - short recordVersion; //probably - short recordType; //probably: 1=sleep, 2=deep sleep - - if (0 != (length % itemSize)) - return false;//malformed message? - - int recordCount = length / itemSize; - SleepRecord84[] sleepRecords = new SleepRecord84[recordCount]; - - for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { - beginOfRecordPosition = initialPosition + recordIdx * itemSize; - datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record - recordVersion = datalogMessage.getShort(); - if (recordVersion != 1) - return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it - - datalogMessage.getShort();//throwaway, unknown - recordType = datalogMessage.getShort(); - - sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); + if (!isPebbleHealthEnabled()) { + return false; } - return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. - } - - private boolean store84(SleepRecord84[] sleepRecords) { - try (DBHandler dbHandler = GBApplication.acquireDB()) { - SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession()); - int latestTimestamp = sampleProvider.fetchLatestTimestamp(); - for (SleepRecord84 sleepRecord : sleepRecords) { - if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds)) - return false; - if (sleepRecord.type == 2) { - sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP)); - } else { - sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP)); - } - - } - } catch (Exception ex) { - LOG.debug(ex.getMessage()); - } - return true; - } - - private boolean handleMessage83(ByteBuffer datalogMessage, int length) { int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably @@ -94,7 +38,7 @@ class DatalogSessionHealthSleep extends DatalogSession { return false;//malformed message? int recordCount = length / itemSize; - SleepRecord83[] sleepRecords = new SleepRecord83[recordCount]; + SleepRecord[] sleepRecords = new SleepRecord[recordCount]; for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { beginOfRecordPosition = initialPosition + recordIdx * itemSize; @@ -103,21 +47,20 @@ class DatalogSessionHealthSleep extends DatalogSession { if (recordVersion != 1) return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it - sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(), + sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); } - return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. + return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. } - private boolean store83(SleepRecord83[] sleepRecords) { + private boolean store(SleepRecord[] sleepRecords) { try (DBHandler dbHandler = GBApplication.acquireDB()) { SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession()); - GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO); int latestTimestamp = sampleProvider.fetchLatestTimestamp(); - for (SleepRecord83 sleepRecord : sleepRecords) { + for (SleepRecord sleepRecord : sleepRecords) { if (latestTimestamp < sleepRecord.bedTimeEnd) return false; sampleProvider.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP)); @@ -128,13 +71,13 @@ class DatalogSessionHealthSleep extends DatalogSession { return true; } - private class SleepRecord83 { + private class SleepRecord { int offsetUTC; //probably int bedTimeStart; int bedTimeEnd; int deepSleepSeconds; - public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { + public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) { this.offsetUTC = offsetUTC; this.bedTimeStart = bedTimeStart; this.bedTimeEnd = bedTimeEnd; @@ -142,17 +85,4 @@ class DatalogSessionHealthSleep extends DatalogSession { } } - private class SleepRecord84 { - int type; //1=sleep, 2=deep sleep - int offsetUTC; //probably - int timestampStart; - int durationSeconds; - - public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) { - this.type = type; - this.offsetUTC = offsetUTC; - this.timestampStart = timestampStart; - this.durationSeconds = durationSeconds; - } - } } \ No newline at end of file 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 a314a674b..408555a29 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 @@ -20,7 +20,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); private final GBDevice device; @@ -35,6 +35,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 000000000..acbbe9f7d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java @@ -0,0 +1,20 @@ +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 { + + DatalogSessionPebbleHealth(byte id, UUID uuid, int tag, byte itemType, short itemSize) { + super(id, uuid, tag, itemType, itemSize); + } + + protected boolean isPebbleHealthEnabled() { + Prefs prefs = GBApplication.getPrefs(); + int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); + return (activityTracker == SampleProvider.PROVIDER_PEBBLE_HEALTH); + } +} \ 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 43b86e2f1..fcc196412 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 @@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; @@ -48,9 +47,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class PebbleIoThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class); - private static final UUID PEBBLE_UUID_RECONNECT = UUID.fromString("00000000-deca-fade-deca-deafdecacafe"); - private static final UUID PEBBLE_UUID_RECONNECT3X = UUID.fromString("a924496e-cc7c-4dff-8a9f-9a76cc2e9d50"); - public static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED"; public static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED"; public static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK"; @@ -71,7 +67,6 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean mIsTCP = false; private BluetoothAdapter mBtAdapter = null; private BluetoothSocket mBtSocket = null; - private BluetoothServerSocket mBtServerSocket = null; private Socket mTCPSocket = null; // for emulator private InputStream mInStream = null; private OutputStream mOutStream = null; @@ -166,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(":"); @@ -193,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; @@ -202,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; @@ -215,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) { @@ -365,9 +363,10 @@ public class PebbleIoThread extends GBDeviceIoThread { LOG.info(e.getMessage()); mIsConnected = false; int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10); - if (GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) { - gbDevice.setState(GBDevice.State.CONNECTING); + if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) { + 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 + ")"); @@ -383,33 +382,10 @@ public class PebbleIoThread extends GBDeviceIoThread { } } } - if (!mIsConnected && !mQuit) { - try { - gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); - gbDevice.sendDeviceUpdateIntent(getContext()); - UUID reconnectUUID = mPebbleProtocol.isFw3x ? PEBBLE_UUID_RECONNECT3X : PEBBLE_UUID_RECONNECT; - mBtServerSocket = mBtAdapter.listenUsingRfcommWithServiceRecord("PebbleReconnectListener", reconnectUUID); - mBtSocket = mBtServerSocket.accept(); - LOG.info("incoming connection on reconnect uuid (" + reconnectUUID + "), will connect actively"); - mBtSocket.close(); - mIsConnected = connect(gbDevice.getAddress()); - } catch (IOException ex) { - ex.printStackTrace(); - LOG.info("error while reconnecting"); - } finally { - try { - if (mBtServerSocket != null) { - mBtServerSocket.close(); - mBtServerSocket = null; - } - } catch (IOException ignore) { - } - } - } if (!mIsConnected) { mBtSocket = null; LOG.info("Bluetooth socket closed, will quit IO Thread"); - mQuit = true; + break; } } } @@ -421,10 +397,16 @@ public class PebbleIoThread extends GBDeviceIoThread { } catch (IOException e) { e.printStackTrace(); } + mBtSocket = null; } + enablePebbleKitReceiver(false); - mBtSocket = null; - gbDevice.setState(GBDevice.State.NOT_CONNECTED); + + if (mQuit) { + gbDevice.setState(GBDevice.State.NOT_CONNECTED); + } else { + gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); + } gbDevice.sendDeviceUpdateIntent(getContext()); } @@ -700,13 +682,6 @@ public class PebbleIoThread extends GBDeviceIoThread { e.printStackTrace(); } } - if (mBtServerSocket != null) { - try { - mBtServerSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } if (mTCPSocket != null) { try { mTCPSocket.close(); 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 d73b9dc40..83f0b090f 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 @@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; 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; @@ -241,6 +242,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 @@ -452,7 +455,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 @@ -465,6 +467,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(); @@ -743,11 +768,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; @@ -1235,10 +1259,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(); } @@ -1803,7 +1827,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) { @@ -1881,9 +1905,11 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size); if (!mDatalogSessions.containsKey(id)) { if (uuid.equals(UUID_ZERO) && log_tag == 81) { - mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice())); - } else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) { + mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice()))); + } else if (uuid.equals(UUID_ZERO) && log_tag == 83) { mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size)); + } else if (uuid.equals(UUID_ZERO) && log_tag == 84) { + mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size)); } else { mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size)); } @@ -2091,8 +2117,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 0087ff733..042874d3a 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; @@ -78,6 +79,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { private boolean reconnect() { if (!isConnected() && useAutoConnect()) { if (getDevice().getState() == GBDevice.State.WAITING_FOR_RECONNECT) { + gbDeviceIOThread.quit(); gbDeviceIOThread.interrupt(); gbDeviceIOThread = null; if (!connect()) { @@ -118,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 5dfa8b2a5..78e15667d 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 f3ed2063b..c12c65675 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 @@ -4,6 +4,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; public abstract class GBDeviceProtocol { @@ -72,6 +73,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 b354edb74..470ef3929 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/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml index dd5d22c3b..873e0c246 100644 --- a/app/src/main/res/menu/appmanager_context.xml +++ b/app/src/main/res/menu/appmanager_context.xml @@ -6,6 +6,9 @@ + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 121df48e8..9d337f732 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ App Manager Löschen + Löschen und aus dem Zwischenspeicher entfernen Sperre für Benachrichtigungen @@ -27,6 +28,7 @@ Einstellungen Allgemeine Einstellungen Verbinde, wenn Bluetooth eingeschaltet wird + Verbindungen automatisch wiederherstellen Bevorzugter Audioplayer Standard Datum und Zeit @@ -44,6 +46,8 @@ Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden. Andere Benachrichtigungen … auch wenn der Bildschirm an ist + Bitte nicht stören + Stoppe unerwünschte Nachrichten, wenn im \"Nicht Stören\"-Modus immer wenn der Bildschirm aus ist niemals @@ -56,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 @@ -202,8 +212,9 @@ Aktivitätsdaten auf dem Gerät lassen Inkompatible Firmware Diese Firmware ist nicht mit dem Gerät kompatibel + Wecker für zukünftige Ereignisse vormerken Verwende den Herzfrequenzsensor um die Schlaferkennung zu verbessern - warte auf eingehende Verbindung + warte auf Verbindung Erneut installieren Über Dich Geburtsjahr @@ -214,6 +225,7 @@ deaktivieren Authentifiziere Authentifizierung erforderlich + Konfigurieren Zzz Widget hinzufügen Gewünschte Schlafdauer in Stunden @@ -226,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 3f1e31357..6ca7ee3b6 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 864ce5412..ccdfc41a4 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,6 +14,7 @@ アプリ管理画面 削除 + キャッシュから削除 ステータス通知ブラックリスト @@ -27,6 +28,7 @@ 設定 一般設定 Bluetoothがオンになったときにデバイスに接続 + 自動的に再接続 お好みのオーディオプレイヤー デフォルト 日付と時刻 @@ -44,6 +46,8 @@ インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。 一般ステータス通知対応 … スクリーンがオンのときにも + サイレント + サイレントモードに基づいて、送信される不要な通知を停止します。 いつも スクリーンがオフのとき なし @@ -56,6 +60,12 @@ お好みのアクティビティ トラッカー 第三者のアンドロイドアップにアクセス権利を与える PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします + 日の出と日の入り + 場所に基づいて、Pebble のタイムラインに日の出・日の入りの時間を送ります + 場所 + 場所の取得 + 緯度 + 経度 通知プロトコルを強制する このオプションを指定すると、ファームウェアのバージョンに応じて強制的に最新の通知プロトコルを使用します。何をしているかわかっている場合のみ有効にしてください! 未テストの機能を有効にする @@ -228,5 +238,4 @@ ファームウェアを送信しませんでした 心拍数 心拍数 - 自動的に再接続 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2afb4a029..b37351454 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 937c29f23..8cf1b2874 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 95f0f1884..988e59050 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ App Manager Delete + Delete and remove from cache Notification Blacklist @@ -34,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 @@ -71,11 +77,21 @@ Preferred Activitytracker 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 @@ -223,6 +239,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 @@ -250,6 +267,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 425e5c4a0..a49185489 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,22 @@ + + 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 + 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 + Honour "Do Not Disturb" for phone calls and SMS + Pebble: fix music information being messed up + Pebble: hopefully fix some reconnect issues Mi Band: fix live activity monitoring running forever if back button pressed diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml index 37e5d7777..548884269 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" /> + + + + + + + + + + 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; + } + } +}