diff --git a/CHANGELOG.md b/CHANGELOG.md index 949564e43..aa1ae6d9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ ###Changelog ####Version 0.16.0 -* New device: ZeBand -* ZeBand: Initial experimental support -* Pebble 2: Fix Pebble Classic FW 3.x app variant being priorized over native Pebble 2 app variant +* New devices: HPlus (e.g. Zeblaze ZeBand), contributed by João Paulo Barraca +* ZeBand: Initial support: notifications, heart rate, sleep monitoring, user configuration, date+time +* Pebble 2: Fix Pebble Classic FW 3.x app variant being prioritized over native Pebble 2 app variant +* Charts (Live Activity): Fix axis labels color in dark theme +* Mi Band: Fix ginormous step count when using Live Activity +* Mi Band: Improved performance during activity sync +* Mi Band 2: Fix activity data missing after doing manual hr measurements or live activity +* Support sharing firmwares/watchapps/watchfaces to Gadgetbridge +* Support for the "Subsonic" music player (#474) ####Version 0.15.2 * Mi Band: Fix crash with unknown notification sources diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index e6698fda0..ca95966ba 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -2,7 +2,7 @@ names () { echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n"; - git log --all --format='%aN:%aE' | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$2]+=1;if (length($1) > length(e[$2])) {e[$2]=$1}}END{for (i in e) { n[e[i]]=i;c[e[i]]+=ct[i] }; for (a in n) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2- + git log --format='%aN:%aE' origin/master | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$2]+=1;if (length($1) > length(e[$2])) {e[$2]=$1}}END{for (i in e) { n[e[i]]=i;c[e[i]]+=ct[i] }; for (a in n) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2- } quine () { @@ -26,15 +26,17 @@ * Carsten Pfeiffer * Daniele Gobbetti * Julien Pivotto +* João Paulo Barraca * Steffen Liebergeld * Lem Dulfo * Sergey Trofimov * JohnnySun * Uwe Hermann -* Gergely Peidl * 0nse <0nse@users.noreply.github.com> +* Gergely Peidl * Christian Fischer * Normano64 +* 6arms1leg * Ⲇⲁⲛⲓ Φi * xzovy * xphnx @@ -56,7 +58,6 @@ * atkyritsis * andre * Alexey Afanasev -* 6arms1leg And all the Transifex translators, which I cannot automatically list, at the moment. diff --git a/README.md b/README.md index 85dd7da02..53585a88f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ need to create an account and transmit any of your data to the vendor's servers. * Mi Band 2 * Vibratissimo (experimental) * Liveview +* HPlus Devices (e.g. ZeBand) ## Features (Pebble) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf45f5fb2..e1b691f81 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -212,6 +212,21 @@ + + + + + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java index 3c0de5ae0..3e68b2dfa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java @@ -199,6 +199,9 @@ public abstract class AbstractAppManagerFragment extends Fragment { if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) { cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM)); } + if (baseName.equals(PebbleProtocol.UUID_WEATHER.toString())) { + cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + } } } if (uuids == null) { @@ -292,6 +295,10 @@ public abstract class AbstractAppManagerFragment extends Fragment { menu.removeItem(R.id.appmanager_hrm_activate); menu.removeItem(R.id.appmanager_hrm_deactivate); } + if (!PebbleProtocol.UUID_WEATHER.equals(selectedApp.getUUID())) { + menu.removeItem(R.id.appmanager_weather_activate); + menu.removeItem(R.id.appmanager_weather_deactivate); + } if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM || selectedApp.getType() == GBDeviceApp.Type.WATCHFACE_SYSTEM) { menu.removeItem(R.id.appmanager_app_delete); } @@ -367,8 +374,12 @@ public abstract class AbstractAppManagerFragment extends Fragment { case R.id.appmanager_hrm_activate: GBApplication.deviceService().onInstallApp(Uri.parse("fake://hrm")); return true; + case R.id.appmanager_weather_activate: + GBApplication.deviceService().onInstallApp(Uri.parse("fake://weather")); + return true; case R.id.appmanager_health_deactivate: case R.id.appmanager_hrm_deactivate: + case R.id.appmanager_weather_deactivate: GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); return true; case R.id.appmanager_app_configure: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java index 6cf8bfbbc..fe53c32ca 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java @@ -28,6 +28,9 @@ public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment if (PebbleUtils.hasHRM(mGBDevice.getModel())) { systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); } + if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) { + systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + } } return systemApps; 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 2e6527884..00374072c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -12,6 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; /** * Specifies all events that Gadgetbridge intends to send to the gadget device. @@ -75,4 +76,6 @@ public interface EventHandler { void onSendConfiguration(String config); void onTestNewFunction(); + + void onSendWeather(WeatherSpec weatherSpec); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java index b328bafaf..76faf56e1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/AbstractMiBandFWHelper.java @@ -15,6 +15,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; /** * Also see Mi1SFirmwareInfo. @@ -26,12 +27,13 @@ public abstract class AbstractMiBandFWHelper { private final byte[] fw; public AbstractMiBandFWHelper(Uri uri, Context context) throws IOException { + UriHelper uriHelper = UriHelper.get(uri, context); String pebblePattern = ".*\\.(pbw|pbz|pbl)"; - if (uri.getPath().matches(pebblePattern)) { + if (uriHelper.getFileName().matches(pebblePattern)) { throw new IOException("Firmware has a filename that looks like a Pebble app/firmware."); } - try (InputStream in = new BufferedInputStream(context.getContentResolver().openInputStream(uri))) { + try (InputStream in = new BufferedInputStream(uriHelper.openInputStream())) { this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB determineFirmwareInfo(fw); } catch (IOException ex) { 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 70ed7f373..7e9bf7e4d 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 @@ -60,6 +60,10 @@ public class PBWInstallHandler implements InstallHandler { installActivity.setInfoText("file not found"); installActivity.setInstallEnabled(false); return; + } catch (IOException e) { + installActivity.setInfoText("error reading file"); + installActivity.setInstallEnabled(false); + return; } if (!mPBWReader.isValid()) { @@ -168,18 +172,22 @@ public class PBWInstallHandler implements InstallHandler { } InputStream jsConfigFile = mPBWReader.getInputStreamFile("pebble-js-app.js"); - if (jsConfigFile != null) { - outputFile = new File(destDir, app.getUUID().toString() + "_config.js"); try { + outputFile = new File(destDir, app.getUUID().toString() + "_config.js"); FileUtils.copyStreamToFile(jsConfigFile, outputFile); } catch (IOException e) { LOG.error("Failed to open output file: " + e.getMessage(), e); + } finally { + try { + jsConfigFile.close(); + } catch (IOException e) { + } } } - } + @Override public boolean isValid() { // always pretend it is valid, as we can't know yet about hw/fw version return true; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index 508f3a7f2..50f8ef9d2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -1,19 +1,14 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble; -import android.content.ContentResolver; import android.content.Context; -import android.database.Cursor; import android.net.Uri; -import android.provider.MediaStore; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -28,6 +23,7 @@ import java.util.zip.ZipInputStream; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.UriHelper; public class PBWReader { private static final Logger LOG = LoggerFactory.getLogger(PBWReader.class); @@ -47,8 +43,7 @@ public class PBWReader { fwFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_SYSRESOURCES); } - private final Uri uri; - private final ContentResolver cr; + private final UriHelper uriHelper; private GBDeviceApp app; private ArrayList pebbleInstallables = null; private boolean isFirmware = false; @@ -59,121 +54,48 @@ public class PBWReader { private short mAppVersion; private int mIconId; private int mFlags; - private long fileSize; private JSONObject mAppKeys = null; - public PBWReader(Uri uri, Context context, String platform) throws FileNotFoundException { - this.uri = uri; - cr = context.getContentResolver(); + public PBWReader(Uri uri, Context context, String platform) throws IOException { + uriHelper = UriHelper.get(uri, context); - InputStream fin = new BufferedInputStream(cr.openInputStream(uri)); - - Uri filePathUri = uri; - if (uri.getScheme().toString().compareTo("content") == 0) { - Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); - if (cursor.moveToFirst()) { - int name_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); - int size_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE); - filePathUri = Uri.parse(cursor.getString(name_index)); - fileSize = cursor.getLong(size_index); - } - } - if (filePathUri.toString().endsWith(".pbl")) { + if (uriHelper.getFileName().endsWith(".pbl")) { STM32CRC stm32crc = new STM32CRC(); - try { + try (InputStream fin = uriHelper.openInputStream()) { byte[] buf = new byte[2000]; while (fin.available() > 0) { int count = fin.read(buf); stm32crc.addData(buf, count); } - fin.close(); - } catch (IOException e) { - e.printStackTrace(); - return; } - int crc = stm32crc.getResult(); // language file app = new GBDeviceApp(UUID.randomUUID(), "Language File", "unknown", "unknown", GBDeviceApp.Type.UNKNOWN); - if (fileSize == 0) { - File f = new File(uri.getPath()); - fileSize = f.length(); - } pebbleInstallables = new ArrayList<>(); - pebbleInstallables.add(new PebbleInstallable("lang", (int) fileSize, crc, PebbleProtocol.PUTBYTES_TYPE_FILE)); + pebbleInstallables.add(new PebbleInstallable("lang", (int) uriHelper.getFileSize(), crc, PebbleProtocol.PUTBYTES_TYPE_FILE)); isValid = true; isLanguage = true; return; } - String platformDir = ""; - - if (!filePathUri.toString().endsWith(".pbz")) { - /* - * for aplite and basalt it is possible to install 2.x apps which have no subfolder - * we still prefer the subfolders if present. - * chalk needs to be its subfolder - */ - String[] platformDirs; - switch (platform) { - case "basalt": - platformDirs = new String[]{"basalt/"}; - break; - case "chalk": - platformDirs = new String[]{"chalk/"}; - break; - case "diorite": - platformDirs = new String[]{"diorite/", "aplite/"}; - break; - case "emery": - platformDirs = new String[]{"emery/", "basalt/"}; - break; - default: - platformDirs = new String[]{"aplite/"}; - } - - for (String dir : platformDirs) { - InputStream afin = new BufferedInputStream(cr.openInputStream(uri)); - - ZipInputStream zis = new ZipInputStream(afin); - ZipEntry ze; - boolean found = false; - try { - while ((ze = zis.getNextEntry()) != null) { - if (ze.getName().startsWith(dir)) { - platformDir = dir; - found = true; - break; - } - } - zis.close(); - } catch (IOException e) { - e.printStackTrace(); - } - if (found) { - break; - } - } - - if (platform.equals("chalk") && platformDir.equals("")) { - return; - } + String platformDir = determinePlatformDir(uriHelper, platform); + if (platform.equals("chalk") && platformDir.equals("")) { + return; } + LOG.info("using platformdir: '" + platformDir + "'"); String appName = null; String appCreator = null; String appVersion = null; UUID appUUID = null; - ZipInputStream zis = new ZipInputStream(fin); ZipEntry ze; pebbleInstallables = new ArrayList<>(); byte[] buffer = new byte[1024]; int count; - - try { + try (ZipInputStream zis = new ZipInputStream(uriHelper.openInputStream())) { while ((ze = zis.getNextEntry()) != null) { String fileName = ze.getName(); if (fileName.equals(platformDir + "manifest.json")) { @@ -269,7 +191,6 @@ public class PBWReader { // more follows but, not interesting for us } } - zis.close(); if (appUUID != null && appName != null && appCreator != null && appVersion != null) { GBDeviceApp.Type appType = GBDeviceApp.Type.APP_GENERIC; @@ -280,11 +201,58 @@ public class PBWReader { } app = new GBDeviceApp(appUUID, appName, appCreator, appVersion, appType); } - } catch (IOException e) { - e.printStackTrace(); } } + /** + * Determines the platform dir to use for the given uri and platform. + * @param uriHelper + * @param platform + * @return the platform dir to use + * @throws IOException + */ + private String determinePlatformDir(UriHelper uriHelper, String platform) throws IOException { + String platformDir = ""; + + if (uriHelper.getFileName().endsWith(".pbz")) { + return platformDir; + } + /* + * for aplite and basalt it is possible to install 2.x apps which have no subfolder + * we still prefer the subfolders if present. + * chalk needs to be its subfolder + */ + String[] platformDirs; + switch (platform) { + case "basalt": + platformDirs = new String[]{"basalt/"}; + break; + case "chalk": + platformDirs = new String[]{"chalk/"}; + break; + case "diorite": + platformDirs = new String[]{"diorite/", "aplite/"}; + break; + case "emery": + platformDirs = new String[]{"emery/", "basalt/"}; + break; + default: + platformDirs = new String[]{"aplite/"}; + } + + for (String dir : platformDirs) { + try (ZipInputStream zis = new ZipInputStream(uriHelper.openInputStream())) { + ZipEntry ze; + while ((ze = zis.getNextEntry()) != null) { + if (ze.getName().startsWith(dir)) { + return dir; + } + } + } + } + return platformDir; + } + public boolean isFirmware() { return isFirmware; } @@ -302,28 +270,29 @@ public class PBWReader { } public InputStream getInputStreamFile(String filename) { - InputStream fin; - try { - fin = new BufferedInputStream(cr.openInputStream(uri)); - if (isLanguage) { - return fin; + if (isLanguage) { + try { + return uriHelper.openInputStream(); + } catch (FileNotFoundException e) { + LOG.warn("file not found: " + e); + return null; } - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; } - ZipInputStream zis = new ZipInputStream(fin); + ZipInputStream zis = null; ZipEntry ze; try { + zis = new ZipInputStream(uriHelper.openInputStream()); while ((ze = zis.getNextEntry()) != null) { if (ze.getName().equals(filename)) { - return zis; + return zis; // return WITHOUT closing the stream! } } zis.close(); } catch (Throwable e) { try { - zis.close(); + if (zis != null) { + zis.close(); + } } catch (IOException e1) { // ignore } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java new file mode 100644 index 000000000..a2b325e35 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationConfig.java @@ -0,0 +1,9 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.app.Activity; + +public class WeatherNotificationConfig extends Activity { + + //TODO: we just need the user to enable us in the weather notification settings. There must be a better way + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java new file mode 100644 index 000000000..c6b39afde --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/WeatherNotificationReceiver.java @@ -0,0 +1,52 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +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.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import ru.gelin.android.weather.notification.ParcelableWeather2; + + +public class WeatherNotificationReceiver extends BroadcastReceiver { + private static final Logger LOG = LoggerFactory.getLogger(WeatherNotificationReceiver.class); + + + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.getAction().contains("WEATHER_UPDATE_2")) { + LOG.info("Wrong action"); + return; + } + ParcelableWeather2 weather = null; + try { + weather = intent.getParcelableExtra("ru.gelin.android.weather.notification.EXTRA_WEATHER"); + } catch (RuntimeException e) { + e.printStackTrace(); + } + + if (weather != null) { + Weather.getInstance().setWeather2(weather); + LOG.info("weather in " + weather.location + " is " + weather.currentCondition + " (" + (weather.currentTemp - 273) + "°C)"); + + WeatherSpec weatherSpec = new WeatherSpec(); + weatherSpec.timestamp = (int) (weather.queryTime / 1000); + weatherSpec.location = weather.location; + weatherSpec.currentTemp = weather.currentTemp; + weatherSpec.currentCondition = weather.currentCondition; + weatherSpec.currentConditionCode = weather.currentConditionCode; + weatherSpec.todayMaxTemp = weather.todayHighTemp; + weatherSpec.todayMinTemp = weather.todayLowTemp; + weatherSpec.tomorrowConditionCode = weather.forecastConditionCode; + weatherSpec.tomorrowMaxTemp = weather.forecastHighTemp; + weatherSpec.tomorrowMinTemp = weather.forecastLowTemp; + Weather.getInstance().setWeatherSpec(weatherSpec); + GBApplication.deviceService().onSendWeather(weatherSpec); + } + } +} \ No newline at end of file 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 8fd47d311..361dbe260 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -17,13 +17,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; -//import java.util.UUID; - public class GBDeviceService implements DeviceService { protected final Context mContext; - protected final Class mServiceClass; + private final Class mServiceClass; public GBDeviceService(Context context) { mContext = context; @@ -293,4 +292,20 @@ public class GBDeviceService implements DeviceService { Intent intent = createIntent().setAction(ACTION_TEST_NEW_FUNCTION); invokeService(intent); } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + Intent intent = createIntent().setAction(ACTION_SEND_WEATHER) + .putExtra(EXTRA_WEATHER_TIMESTAMP, weatherSpec.timestamp) + .putExtra(EXTRA_WEATHER_LOCATION, weatherSpec.location) + .putExtra(EXTRA_WEATHER_CURRENTTEMP, weatherSpec.currentTemp) + .putExtra(EXTRA_WEATHER_CURRENTCONDITIONCODE, weatherSpec.currentConditionCode) + .putExtra(EXTRA_WEATHER_CURRENTCONDITION, weatherSpec.currentCondition) + .putExtra(EXTRA_WEATHER_TODAYMAXTEMP, weatherSpec.todayMaxTemp) + .putExtra(EXTRA_WEATHER_TODAYMINTEMP, weatherSpec.todayMinTemp) + .putExtra(EXTRA_WEATHER_TOMORROWMAXTEMP, weatherSpec.tomorrowMaxTemp) + .putExtra(EXTRA_WEATHER_TOMORROWMINTEMP, weatherSpec.tomorrowMinTemp) + .putExtra(EXTRA_WEATHER_TOMORROWCONDITIONCODE, weatherSpec.tomorrowConditionCode); + invokeService(intent); + } } 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 c36808e5c..b536fefbc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -44,6 +44,7 @@ public interface DeviceService extends EventHandler { String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent"; String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent"; String ACTION_SEND_CONFIGURATION = PREFIX + ".action.send_configuration"; + String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather"; String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function"; String EXTRA_DEVICE_ADDRESS = "device_address"; String EXTRA_NOTIFICATION_BODY = "notification_body"; @@ -80,6 +81,18 @@ public interface DeviceService extends EventHandler { String EXTRA_ALARMS = "alarms"; String EXTRA_PERFORM_PAIR = "perform_pair"; String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; + + String EXTRA_WEATHER_TIMESTAMP = "weather_timestamp"; + String EXTRA_WEATHER_LOCATION = "weather_location"; + String EXTRA_WEATHER_CURRENTTEMP = "weather_currenttemp"; + String EXTRA_WEATHER_CURRENTCONDITIONCODE = "weather_currentconditioncode"; + String EXTRA_WEATHER_CURRENTCONDITION = "currentcondition"; + String EXTRA_WEATHER_TODAYMAXTEMP = "weather_todaymaxtemp"; + String EXTRA_WEATHER_TODAYMINTEMP = "weather_todaymintemp"; + String EXTRA_WEATHER_TOMORROWMAXTEMP = "weather_tomorrowmaxtemp"; + String EXTRA_WEATHER_TOMORROWMINTEMP = "weather_tomorrowmintemp"; + String EXTRA_WEATHER_TOMORROWCONDITIONCODE = "weather_tomorrowconditioncode"; + /** * Use EXTRA_REALTIME_SAMPLE instead */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java new file mode 100644 index 000000000..979a6dc69 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Weather.java @@ -0,0 +1,351 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +import ru.gelin.android.weather.notification.ParcelableWeather2; + +public class Weather { + private ParcelableWeather2 weather2 = null; + private WeatherSpec weatherSpec = null; + + public ParcelableWeather2 getWeather2() { + return weather2; + } + + public void setWeather2(ParcelableWeather2 weather2) { + this.weather2 = weather2; + } + + public WeatherSpec getWeatherSpec() { + return weatherSpec; + } + + public void setWeatherSpec(WeatherSpec weatherSpec) { + this.weatherSpec = weatherSpec; + } + + private static final Weather weather = new Weather(); + public static Weather getInstance() {return weather;} + + public static byte mapToPebbleCondition(int openWeatherMapCondition) { +/* deducted values: + 0 = sun + cloud + 1 = clouds + 2 = some snow + 3 = some rain + 4 = heavy rain + 5 = heavy snow + 6 = sun + cloud + rain (default icon?) + 7 = sun + 8 = rain + snow + 9 = 6 + 10, 11, ... = empty icon + */ + switch (openWeatherMapCondition) { +//Group 2xx: Thunderstorm + case 200: //thunderstorm with light rain: //11d + case 201: //thunderstorm with rain: //11d + case 202: //thunderstorm with heavy rain: //11d + case 210: //light thunderstorm:: //11d + case 211: //thunderstorm: //11d + case 230: //thunderstorm with light drizzle: //11d + case 231: //thunderstorm with drizzle: //11d + case 232: //thunderstorm with heavy drizzle: //11d + case 212: //heavy thunderstorm: //11d + case 221: //ragged thunderstorm: //11d + return 4; +//Group 3xx: Drizzle + case 300: //light intensity drizzle: //09d + case 301: //drizzle: //09d + case 302: //heavy intensity drizzle: //09d + case 310: //light intensity drizzle rain: //09d + case 311: //drizzle rain: //09d + case 312: //heavy intensity drizzle rain: //09d + case 313: //shower rain and drizzle: //09d + case 314: //heavy shower rain and drizzle: //09d + case 321: //shower drizzle: //09d + case 500: //light rain: //10d + case 501: //moderate rain: //10d + return 3; +//Group 5xx: Rain + case 502: //heavy intensity rain: //10d + case 503: //very heavy rain: //10d + case 504: //extreme rain: //10d + case 511: //freezing rain: //13d + case 520: //light intensity shower rain: //09d + case 521: //shower rain: //09d + case 522: //heavy intensity shower rain: //09d + case 531: //ragged shower rain: //09d + return 4; +//Group 6xx: Snow + case 600: //light snow: //[[file:13d.png]] + case 601: //snow: //[[file:13d.png]] + case 620: //light shower snow: //[[file:13d.png]] + return 2; + case 602: //heavy snow: //[[file:13d.png]] + case 611: //sleet: //[[file:13d.png]] + case 612: //shower sleet: //[[file:13d.png]] + case 621: //shower snow: //[[file:13d.png]] + case 622: //heavy shower snow: //[[file:13d.png]] + return 5; + case 615: //light rain and snow: //[[file:13d.png]] + case 616: //rain and snow: //[[file:13d.png]] + return 8; +//Group 7xx: Atmosphere + case 701: //mist: //[[file:50d.png]] + case 711: //smoke: //[[file:50d.png]] + case 721: //haze: //[[file:50d.png]] + case 731: //sandcase dust whirls: //[[file:50d.png]] + case 741: //fog: //[[file:50d.png]] + case 751: //sand: //[[file:50d.png]] + case 761: //dust: //[[file:50d.png]] + case 762: //volcanic ash: //[[file:50d.png]] + case 771: //squalls: //[[file:50d.png]] + case 781: //tornado: //[[file:50d.png]] + case 900: //tornado + return 6; +//Group 800: Clear + case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]] + return 7; +//Group 80x: Clouds + case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]] + case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]] + case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]] + case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]] + return 0; +//Group 90x: Extreme + case 901: //tropical storm + case 903: //cold + case 904: //hot + case 905: //windy + case 906: //hail +//Group 9xx: Additional + case 951: //calm + case 952: //light breeze + case 953: //gentle breeze + case 954: //moderate breeze + case 955: //fresh breeze + case 956: //strong breeze + case 957: //high windcase near gale + case 958: //gale + case 959: //severe gale + case 960: //storm + case 961: //violent storm + case 902: //hurricane + case 962: //hurricane + default: + return 6; + + } + } + public static int mapToYahooCondition(int openWeatherMapCondition) { + // openweathermap.org conditions: + // http://openweathermap.org/weather-conditions + switch (openWeatherMapCondition) { +//Group 2xx: Thunderstorm + case 200: //thunderstorm with light rain: //11d + case 201: //thunderstorm with rain: //11d + case 202: //thunderstorm with heavy rain: //11d + case 210: //light thunderstorm:: //11d + case 211: //thunderstorm: //11d + case 230: //thunderstorm with light drizzle: //11d + case 231: //thunderstorm with drizzle: //11d + case 232: //thunderstorm with heavy drizzle: //11d + return 4; + case 212: //heavy thunderstorm: //11d + case 221: //ragged thunderstorm: //11d + return 3; +//Group 3xx: Drizzle + case 300: //light intensity drizzle: //09d + case 301: //drizzle: //09d + case 302: //heavy intensity drizzle: //09d + case 310: //light intensity drizzle rain: //09d + case 311: //drizzle rain: //09d + case 312: //heavy intensity drizzle rain: //09d + return 9; + case 313: //shower rain and drizzle: //09d + case 314: //heavy shower rain and drizzle: //09d + case 321: //shower drizzle: //09d + return 11; +//Group 5xx: Rain + case 500: //light rain: //10d + case 501: //moderate rain: //10d + case 502: //heavy intensity rain: //10d + case 503: //very heavy rain: //10d + case 504: //extreme rain: //10d + case 511: //freezing rain: //13d + return 10; + case 520: //light intensity shower rain: //09d + return 40; + case 521: //shower rain: //09d + case 522: //heavy intensity shower rain: //09d + case 531: //ragged shower rain: //09d + return 12; +//Group 6xx: Snow + case 600: //light snow: //[[file:13d.png]] + return 7; + case 601: //snow: //[[file:13d.png]] + return 16; + case 602: //heavy snow: //[[file:13d.png]] + return 15; + case 611: //sleet: //[[file:13d.png]] + case 612: //shower sleet: //[[file:13d.png]] + return 18; + case 615: //light rain and snow: //[[file:13d.png]] + case 616: //rain and snow: //[[file:13d.png]] + return 5; + case 620: //light shower snow: //[[file:13d.png]] + return 14; + case 621: //shower snow: //[[file:13d.png]] + return 46; + case 622: //heavy shower snow: //[[file:13d.png]] +//Group 7xx: Atmosphere + case 701: //mist: //[[file:50d.png]] + case 711: //smoke: //[[file:50d.png]] + return 22; + case 721: //haze: //[[file:50d.png]] + return 21; + case 731: //sandcase dust whirls: //[[file:50d.png]] + return 3200; + case 741: //fog: //[[file:50d.png]] + return 20; + case 751: //sand: //[[file:50d.png]] + case 761: //dust: //[[file:50d.png]] + return 19; + case 762: //volcanic ash: //[[file:50d.png]] + case 771: //squalls: //[[file:50d.png]] + return 3200; + case 781: //tornado: //[[file:50d.png]] + case 900: //tornado + return 0; +//Group 800: Clear + case 800: //clear sky: //[[file:01d.png]] [[file:01n.png]] + return 32; +//Group 80x: Clouds + case 801: //few clouds: //[[file:02d.png]] [[file:02n.png]] + case 802: //scattered clouds: //[[file:03d.png]] [[file:03d.png]] + return 34; + case 803: //broken clouds: //[[file:04d.png]] [[file:03d.png]] + case 804: //overcast clouds: //[[file:04d.png]] [[file:04d.png]] + return 44; +//Group 90x: Extreme + case 901: //tropical storm + return 1; + case 903: //cold + return 25; + case 904: //hot + return 36; + case 905: //windy + return 24; + case 906: //hail + return 17; +//Group 9xx: Additional + case 951: //calm + case 952: //light breeze + case 953: //gentle breeze + case 954: //moderate breeze + case 955: //fresh breeze + return 34; + case 956: //strong breeze + case 957: //high windcase near gale + return 24; + case 958: //gale + case 959: //severe gale + case 960: //storm + case 961: //violent storm + return 3200; + case 902: //hurricane + case 962: //hurricane + return 2; + default: + return 3200; + + } + } + + public static int mapToOpenWeatherMapCondition(int yahooCondition) { + switch (yahooCondition) { +//yahoo weather conditions: +//https://developer.yahoo.com/weather/documentation.html + case 0: //tornado + return 900; + case 1: //tropical storm + return 901; + case 2: //hurricane + return 962; + case 3: //severe thunderstorms + return 212; + case 4: //thunderstorms + return 211; + case 5: //mixed rain and snow + case 6: //mixed rain and sleet + return 616; + case 7: //mixed snow and sleet + return 600; + case 8: //freezing drizzle + case 9: //drizzle + return 301; + case 10: //freezing rain + return 511; + case 11: //showers + case 12: //showers + return 521; + case 13: //snow flurries + case 14: //light snow showers + return 620; + case 15: //blowing snow + case 41: //heavy snow + case 42: //scattered snow showers + case 43: //heavy snow + case 46: //snow showers + return 602; + case 16: //snow + return 601; + case 17: //hail + case 35: //mixed rain and hail + return 906; + case 18: //sleet + return 611; + case 19: //dust + return 761; + case 20: //foggy + return 741; + case 21: //haze + return 721; + case 22: //smoky + return 711; + case 23: //blustery + case 24: //windy + return 905; + case 25: //cold + return 903; + case 26: //cloudy + case 27: //mostly cloudy (night) + case 28: //mostly cloudy (day) + return 804; + case 29: //partly cloudy (night) + case 30: //partly cloudy (day) + return 801; + case 31: //clear (night) + case 32: //sunny + return 800; + case 33: //fair (night) + case 34: //fair (day) + return 801; + case 36: //hot + return 904; + case 37: //isolated thunderstorms + case 38: //scattered thunderstorms + case 39: //scattered thunderstorms + return 210; + case 40: //scattered showers + return 520; + case 44: //partly cloudy + return 801; + case 45: //thundershowers + case 47: //isolated thundershowers + return 621; + case 3200: //not available + default: + return -1; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java new file mode 100644 index 000000000..cd30af406 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/WeatherSpec.java @@ -0,0 +1,14 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class WeatherSpec { + public int timestamp; + public String location; + public int currentTemp; + public int currentConditionCode; + public String currentCondition; + public int todayMaxTemp; + public int todayMinTemp; + public int tomorrowMaxTemp; + public int tomorrowMinTemp; + public int tomorrowConditionCode; +} 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 9e90bfea3..f4417fb70 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -47,6 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; @@ -73,6 +74,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RE import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_CONFIGURATION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SEND_WEATHER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETCANNEDMESSAGES; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE; @@ -123,6 +125,16 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_CURRENTCONDITION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_CURRENTCONDITIONCODE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_CURRENTTEMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_LOCATION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TIMESTAMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TODAYMAXTEMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TODAYMINTEMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TOMORROWCONDITIONCODE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TOMORROWMAXTEMP; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_TOMORROWMINTEMP; public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener { private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class); @@ -501,6 +513,21 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mDeviceSupport.onTestNewFunction(); break; } + case ACTION_SEND_WEATHER: { + WeatherSpec weatherSpec = new WeatherSpec(); + weatherSpec.timestamp = intent.getIntExtra(EXTRA_WEATHER_TIMESTAMP, 0); + weatherSpec.location = intent.getStringExtra(EXTRA_WEATHER_LOCATION); + weatherSpec.currentTemp = intent.getIntExtra(EXTRA_WEATHER_CURRENTTEMP, 0); + weatherSpec.currentConditionCode = intent.getIntExtra(EXTRA_WEATHER_CURRENTCONDITIONCODE, 0); + weatherSpec.currentCondition = intent.getStringExtra(EXTRA_WEATHER_CURRENTCONDITION); + weatherSpec.todayMaxTemp = intent.getIntExtra(EXTRA_WEATHER_TODAYMAXTEMP, 0); + weatherSpec.todayMinTemp = intent.getIntExtra(EXTRA_WEATHER_TODAYMINTEMP, 0); + weatherSpec.tomorrowMaxTemp = intent.getIntExtra(EXTRA_WEATHER_TOMORROWMAXTEMP, 0); + weatherSpec.tomorrowMinTemp = intent.getIntExtra(EXTRA_WEATHER_TOMORROWMINTEMP, 0); + weatherSpec.tomorrowConditionCode = intent.getIntExtra(EXTRA_WEATHER_TOMORROWCONDITIONCODE, 0); + mDeviceSupport.onSendWeather(weatherSpec); + break; + } } return START_STICKY; 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 a90ee63c5..437bf5c7f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -19,6 +19,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; /** * Wraps another device support instance and supports busy-checking and throttling of events. @@ -335,4 +336,12 @@ public class ServiceDeviceSupport implements DeviceSupport { } delegate.onTestNewFunction(); } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + if (checkBusy("send weather event")) { + return; + } + delegate.onSendWeather(weatherSpec); + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index c3704ffec..be083b070 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java @@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; @@ -637,6 +638,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { LOG.debug("Test New Function"); } + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + private void showIncomingCall(String name, String number){ LOG.debug("Show Incoming Call"); 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 23d7bb234..1971c0735 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 @@ -50,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic; @@ -1206,6 +1207,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } } + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + private void handleSensorData(byte[] value) { int counter=0, step=0, axis1=0, axis2=0, axis3 =0; if ((value.length - 2) % 6 != 0) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java index a89ff44c4..b89e9e089 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java @@ -57,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; @@ -1246,6 +1247,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { public void onTestNewFunction() { } + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + private MiBand2Support setDateDisplay(TransactionBuilder builder) { DateTimeDisplay dateTimeDisplay = MiBand2Coordinator.getDateDisplay(getContext()); LOG.info("Setting date display to " + dateTimeDisplay); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java index 7f32886e9..cc70c4398 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java @@ -2,6 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; import android.widget.Toast; import org.slf4j.Logger; @@ -23,7 +25,6 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; @@ -73,7 +74,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation { builder.notify(characteristicFetch, true); BluetoothGattCharacteristic characteristicActivityData = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA); - GregorianCalendar sinceWhen = getLastSuccessfulSynchronizedTime(); + GregorianCalendar sinceWhen = getLastSuccessfulSyncTime(); builder.write(characteristicFetch, BLETypeConversions.join(new byte[] { MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE, 0x01 }, getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES))); builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply builder.notify(characteristicActivityData, true); @@ -81,26 +82,28 @@ public class FetchActivityOperation extends AbstractMiBand2Operation { builder.queue(getQueue()); } - private GregorianCalendar getLastSuccessfulSynchronizedTime() { - try (DBHandler dbHandler = GBApplication.acquireDB()) { - DaoSession session = dbHandler.getDaoSession(); - SampleProvider sampleProvider = new MiBand2SampleProvider(getDevice(), session); - MiBandActivitySample sample = sampleProvider.getLatestActivitySample(); - if (sample != null) { - int timestamp = sample.getTimestamp(); - GregorianCalendar calendar = BLETypeConversions.createCalendar(); - calendar.setTimeInMillis((long) timestamp * 1000); - return calendar; - } - } catch (Exception ex) { - LOG.error("Error querying for latest activity sample, synchronizing the last 10 days", ex); + private GregorianCalendar getLastSuccessfulSyncTime() { + long timeStampMillis = GBApplication.getPrefs().getLong(getLastSyncTimeKey(), 0); + if (timeStampMillis != 0) { + GregorianCalendar calendar = BLETypeConversions.createCalendar(); + calendar.setTimeInMillis(timeStampMillis); + return calendar; } - GregorianCalendar calendar = BLETypeConversions.createCalendar(); calendar.add(Calendar.DAY_OF_MONTH, -10); return calendar; } + private void saveLastSyncTimestamp(@NonNull GregorianCalendar timestamp) { + SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit(); + editor.putLong(getLastSyncTimeKey(), timestamp.getTimeInMillis()); + editor.apply(); + } + + private String getLastSyncTimeKey() { + return getDevice().getAddress() + "_" + "lastSyncTimeMillis"; + } + @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { @@ -147,6 +150,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation { } sampleProvider.addGBActivitySamples(samples.toArray(new MiBandActivitySample[0])); + saveLastSyncTimestamp(timestamp); LOG.info("Mi2 activity data: last sample timestamp: " + DateTimeUtils.formatDateTime(timestamp.getTime())); } catch (Exception ex) { 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 1944f2d5f..042c7e509 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 @@ -8,10 +8,11 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; -public class AppMessageHandler { - protected final PebbleProtocol mPebbleProtocol; - protected final UUID mUUID; +class AppMessageHandler { + final PebbleProtocol mPebbleProtocol; + final UUID mUUID; AppMessageHandler(UUID uuid, PebbleProtocol pebbleProtocol) { mUUID = uuid; @@ -30,7 +31,11 @@ public class AppMessageHandler { return null; } - public GBDeviceEvent[] pushMessage() { + public GBDeviceEvent[] onAppStart() { + return null; + } + + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { return null; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java new file mode 100644 index 000000000..178b499ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import android.util.Pair; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; + +class AppMessageHandlerHealthify extends AppMessageHandler { + private static final int KEY_TEMPERATURE = 10021; + private static final int KEY_CONDITIONS = 10022; + + AppMessageHandlerHealthify(UUID uuid, PebbleProtocol pebbleProtocol) { + super(uuid, pebbleProtocol); + } + + private byte[] encodeMarioWeatherMessage(WeatherSpec weatherSpec) { + if (weatherSpec == null) { + return null; + } + + ArrayList> pairs = new ArrayList<>(2); + pairs.add(new Pair<>(KEY_CONDITIONS, (Object) weatherSpec.currentCondition)); + pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273))); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + + ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); + + buf.put(weatherMessage); + + return buf.array(); + } + + @Override + public GBDeviceEvent[] handleMessage(ArrayList> pairs) { + // Just ACK + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); + return new GBDeviceEvent[]{sendBytesAck}; + } + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeMarioWeatherMessage(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeMarioWeatherMessage(weatherSpec); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java new file mode 100644 index 000000000..91b105e48 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java @@ -0,0 +1,63 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import android.util.Pair; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; + +class AppMessageHandlerMarioTime extends AppMessageHandler { + + private static final int KEY_WEATHER_ICON_ID = 10; + private static final int KEY_WEATHER_TEMPERATURE = 11; + + AppMessageHandlerMarioTime(UUID uuid, PebbleProtocol pebbleProtocol) { + super(uuid, pebbleProtocol); + } + + private byte[] encodeMarioWeatherMessage(WeatherSpec weatherSpec) { + if (weatherSpec == null) { + return null; + } + + ArrayList> pairs = new ArrayList<>(2); + pairs.add(new Pair<>(KEY_WEATHER_ICON_ID, (Object) (byte) 1)); + pairs.add(new Pair<>(KEY_WEATHER_TEMPERATURE, (Object) (byte) (weatherSpec.currentTemp - 273))); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + + ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); + + buf.put(weatherMessage); + + return buf.array(); + } + + @Override + public GBDeviceEvent[] handleMessage(ArrayList> pairs) { + // Just ACK + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); + return new GBDeviceEvent[]{sendBytesAck}; + } + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeMarioWeatherMessage(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeMarioWeatherMessage(weatherSpec); + } +} 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 1bb51991b..381743a32 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 @@ -22,21 +22,21 @@ import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMisfitSample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -public class AppMessageHandlerMisfit extends AppMessageHandler { +class AppMessageHandlerMisfit extends AppMessageHandler { - public static final int KEY_SLEEPGOAL = 1; - public static final int KEY_STEP_ROGRESS = 2; - public static final int KEY_SLEEP_PROGRESS = 3; - public static final int KEY_VERSION = 4; - public static final int KEY_SYNC = 5; - public static final int KEY_INCOMING_DATA_BEGIN = 6; - public static final int KEY_INCOMING_DATA = 7; - public static final int KEY_INCOMING_DATA_END = 8; - public static final int KEY_SYNC_RESULT = 9; + private static final int KEY_SLEEPGOAL = 1; + private static final int KEY_STEP_ROGRESS = 2; + private static final int KEY_SLEEP_PROGRESS = 3; + private static final int KEY_VERSION = 4; + private static final int KEY_SYNC = 5; + private static final int KEY_INCOMING_DATA_BEGIN = 6; + private static final int KEY_INCOMING_DATA = 7; + private static final int KEY_INCOMING_DATA_END = 8; + private static final int KEY_SYNC_RESULT = 9; private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMisfit.class); - public AppMessageHandlerMisfit(UUID uuid, PebbleProtocol pebbleProtocol) { + AppMessageHandlerMisfit(UUID uuid, PebbleProtocol pebbleProtocol) { super(uuid, pebbleProtocol); } 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 4f7a14a87..4b152289a 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 @@ -20,37 +20,37 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMorpheuzSampleP import nodomain.freeyourgadget.gadgetbridge.entities.PebbleMorpheuzSample; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; -public class AppMessageHandlerMorpheuz extends AppMessageHandler { +class AppMessageHandlerMorpheuz extends AppMessageHandler { - public static final int KEY_POINT = 1; - public static final int KEY_POINT_46 = 10000; - public static final int KEY_CTRL = 2; - public static final int KEY_CTRL_46 = 10001; - public static final int KEY_FROM = 3; - public static final int KEY_FROM_46 = 10002; - public static final int KEY_TO = 4; - public static final int KEY_TO_46 = 10003; - public static final int KEY_BASE = 5; - public static final int KEY_BASE_46 = 10004; - public static final int KEY_VERSION = 6; - public static final int KEY_VERSION_46 = 10005; - public static final int KEY_GONEOFF = 7; - public static final int KEY_GONEOFF_46 = 10006; - public static final int KEY_TRANSMIT = 8; - public static final int KEY_TRANSMIT_46 = 10007; - public static final int KEY_AUTO_RESET = 9; - public static final int KEY_AUTO_RESET_46 = 10008; - public static final int KEY_SNOOZES = 10; - public static final int KEY_SNOOZES_46 = 10009; - public static final int KEY_FAULT_46 = 10010; + private static final int KEY_POINT = 1; + private static final int KEY_POINT_46 = 10000; + private static final int KEY_CTRL = 2; + private static final int KEY_CTRL_46 = 10001; + private static final int KEY_FROM = 3; + private static final int KEY_FROM_46 = 10002; + private static final int KEY_TO = 4; + private static final int KEY_TO_46 = 10003; + private static final int KEY_BASE = 5; + private static final int KEY_BASE_46 = 10004; + private static final int KEY_VERSION = 6; + private static final int KEY_VERSION_46 = 10005; + private static final int KEY_GONEOFF = 7; + private static final int KEY_GONEOFF_46 = 10006; + private static final int KEY_TRANSMIT = 8; + private static final int KEY_TRANSMIT_46 = 10007; + private static final int KEY_AUTO_RESET = 9; + private static final int KEY_AUTO_RESET_46 = 10008; + private static final int KEY_SNOOZES = 10; + private static final int KEY_SNOOZES_46 = 10009; + private static final int KEY_FAULT_46 = 10010; - public static final int CTRL_TRANSMIT_DONE = 1; - public static final int CTRL_VERSION_DONE = 2; - public static final int CTRL_GONEOFF_DONE = 4; - public static final int CTRL_DO_NEXT = 8; - public static final int CTRL_SET_LAST_SENT = 16; - public static final int CTRL_LAZARUS = 32; - public static final int CTRL_SNOOZES_DONE = 64; + private static final int CTRL_TRANSMIT_DONE = 1; + private static final int CTRL_VERSION_DONE = 2; + private static final int CTRL_GONEOFF_DONE = 4; + private static final int CTRL_DO_NEXT = 8; + private static final int CTRL_SET_LAST_SENT = 16; + private static final int CTRL_LAZARUS = 32; + private static final int CTRL_SNOOZES_DONE = 64; // data received from Morpheuz in native format private int version = 0; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java index 6d7d8502d..8e916c105 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java @@ -11,8 +11,10 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import ru.gelin.android.weather.notification.ParcelableWeather2; -public class AppMessageHandlerPebStyle extends AppMessageHandler { +class AppMessageHandlerPebStyle extends AppMessageHandler { public static final int KEY_AMPM_TEXT = 21; public static final int KEY_BLUETOOTH_ALERT = 2; public static final int KEY_BLUETOOTH_ICON = 20; @@ -64,7 +66,7 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler { pairs.add(new Pair<>(KEY_SIDEBAR_BG_COLOR, (Object) PebbleColor.MediumSpringGreen)); //DIGITAL settings - /* + /* pairs.add(new Pair<>(KEY_MAIN_CLOCK, (Object) 1)); //0 analog pairs.add(new Pair<>(KEY_SECONDARY_INFO_TYPE, (Object) 3)); //1 time, 2 location */ @@ -74,12 +76,13 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler { //WEATHER - /* - //comment the same key in the general section above! - pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 0)); //0 auto, 1 manual - pairs.add(new Pair<>(KEY_WEATHER_CODE, (Object) 3)); - pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) 10)); - */ + ParcelableWeather2 weather = Weather.getInstance().getWeather2(); + if (weather != null) { + //comment the same key in the general section above! + pairs.add(new Pair<>(KEY_LOCATION_SERVICE, (Object) 0)); //0 auto, 1 manual + pairs.add(new Pair<>(KEY_WEATHER_CODE, (Object) Weather.mapToYahooCondition(weather.currentConditionCode))); + pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) (weather.currentTemp - 273))); + } byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); @@ -107,7 +110,7 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler { } @Override - public GBDeviceEvent[] pushMessage() { + public GBDeviceEvent[] onAppStart() { return null; /* GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java index 2ab8ae2d1..16c96afc7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java @@ -1,109 +1,135 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; -import android.graphics.Color; import android.util.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; -public class AppMessageHandlerTimeStylePebble extends AppMessageHandler { - public static final int KEY_SETTING_SIDEBAR_LEFT = 9; - public static final int KEY_CONDITION_CODE = 4; - public static final int KEY_FORECAST_CONDITION = 25; - public static final int KEY_FORECAST_TEMP_HIGH = 26; - public static final int KEY_FORECAST_TEMP_LOW = 27; - public static final int KEY_SETTING_ALTCLOCK_NAME = 28; - public static final int KEY_SETTING_ALTCLOCK_OFFSET = 29; - public static final int KEY_SETTING_BT_VIBE = 11; - public static final int KEY_SETTING_CLOCK_FONT_ID = 18; - public static final int KEY_SETTING_COLOR_BG = 7; - public static final int KEY_SETTING_COLOR_SIDEBAR = 8; - public static final int KEY_SETTING_COLOR_TIME = 6; - public static final int KEY_SETTING_DISABLE_WEATHER = 17; - public static final int KEY_SETTING_HOURLY_VIBE = 19; - public static final int KEY_SETTING_LANGUAGE_ID = 13; - public static final int KEY_SETTING_ONLY_SHOW_BATTERY_WHEN_LOW = 20; - public static final int KEY_SETTING_SHOW_BATTERY_PCT = 16; - public static final int KEY_SETTING_SHOW_LEADING_ZERO = 15; - public static final int KEY_SETTING_SIDEBAR_TEXT_COLOR = 12; - public static final int KEY_SETTING_USE_LARGE_FONTS = 21; - public static final int KEY_SETTING_USE_METRIC = 10; - public static final int KEY_TEMPERATURE = 3; - public static final int KEY_USE_NIGHT_ICON = 5; - public static final int KEY_WIDGET_0_ID = 22; - public static final int KEY_WIDGET_1_ID = 23; - public static final int KEY_WIDGET_2_ID = 24; +class AppMessageHandlerTimeStylePebble extends AppMessageHandler { + private static final int MESSAGE_KEY_WeatherCondition = 10000; + private static final int MESSAGE_KEY_WeatherForecastCondition = 10002; + private static final int MESSAGE_KEY_WeatherForecastHighTemp = 10003; + private static final int MESSAGE_KEY_WeatherForecastLowTemp = 10004; + private static final int MESSAGE_KEY_WeatherTemperature = 10001; + private static final int MESSAGE_KEY_WeatherUseNightIcon = 10025; - private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerTimeStylePebble.class); + private static final int ICON_CLEAR_DAY = 0; + private static final int ICON_CLEAR_NIGHT = 1; + private static final int ICON_CLOUDY_DAY = 2; + private static final int ICON_HEAVY_RAIN = 3; + private static final int ICON_HEAVY_SNOW = 4; + private static final int ICON_LIGHT_RAIN = 5; + private static final int ICON_LIGHT_SNOW = 6; + private static final int ICON_PARTLY_CLOUDY_NIGHT = 7; + private static final int ICON_PARTLY_CLOUDY = 8; + private static final int ICON_RAINING_AND_SNOWING = 9; + private static final int ICON_THUNDERSTORM = 10; + private static final int ICON_WEATHER_GENERIC = 11; - public AppMessageHandlerTimeStylePebble(UUID uuid, PebbleProtocol pebbleProtocol) { + AppMessageHandlerTimeStylePebble(UUID uuid, PebbleProtocol pebbleProtocol) { super(uuid, pebbleProtocol); } - private byte[] encodeTimeStylePebbleConfig() { - ArrayList> pairs = new ArrayList<>(); - //settings that give good legibility on pebble time - pairs.add(new Pair<>(KEY_SETTING_SIDEBAR_LEFT, (Object) 1)); - pairs.add(new Pair<>(KEY_SETTING_CLOCK_FONT_ID, (Object) 1)); - pairs.add(new Pair<>(KEY_SETTING_COLOR_BG, (Object) Color.parseColor("#ffffff"))); - pairs.add(new Pair<>(KEY_SETTING_COLOR_SIDEBAR, (Object) Color.parseColor("#00aaff"))); - pairs.add(new Pair<>(KEY_SETTING_COLOR_TIME, (Object) Color.parseColor("#000000"))); - pairs.add(new Pair<>(KEY_SETTING_SHOW_LEADING_ZERO, (Object) 1)); - pairs.add(new Pair<>(KEY_SETTING_LANGUAGE_ID, (Object) 2)); //2 = Deutsch - pairs.add(new Pair<>(KEY_SETTING_USE_METRIC, (Object) 1)); + /* + * converted to JAVA from original JS + */ + private int getIconForConditionCode(int conditionCode, boolean isNight) { + int generalCondition = conditionCode / 100; + int iconToLoad; + // determine the correct icon + switch (generalCondition) { + case 2: //thunderstorm + iconToLoad = ICON_THUNDERSTORM; + break; + case 3: //drizzle + iconToLoad = ICON_LIGHT_RAIN; + break; + case 5: //rain + if (conditionCode == 500) { + iconToLoad = ICON_LIGHT_RAIN; + } else if (conditionCode < 505) { + iconToLoad = ICON_HEAVY_RAIN; + } else if (conditionCode == 511) { + iconToLoad = ICON_RAINING_AND_SNOWING; + } else { + iconToLoad = ICON_LIGHT_RAIN; + } + break; + case 6: //snow + if (conditionCode == 600 || conditionCode == 620) { + iconToLoad = ICON_LIGHT_SNOW; + } else if (conditionCode > 610 && conditionCode < 620) { + iconToLoad = ICON_RAINING_AND_SNOWING; + } else { + iconToLoad = ICON_HEAVY_SNOW; + } + break; + case 7: // fog, dust, etc + iconToLoad = ICON_CLOUDY_DAY; + break; + case 8: // clouds + if (conditionCode == 800) { + iconToLoad = (!isNight) ? ICON_CLEAR_DAY : ICON_CLEAR_NIGHT; + } else if (conditionCode < 803) { + iconToLoad = (!isNight) ? ICON_PARTLY_CLOUDY : ICON_PARTLY_CLOUDY_NIGHT; + } else { + iconToLoad = ICON_CLOUDY_DAY; + } + break; + default: + iconToLoad = ICON_WEATHER_GENERIC; + break; + } - pairs.add(new Pair<>(KEY_WIDGET_0_ID, (Object) 7)); //7 = current weather - pairs.add(new Pair<>(KEY_WIDGET_1_ID, (Object) 2)); //2 = battery - pairs.add(new Pair<>(KEY_WIDGET_2_ID, (Object) 4)); //4 = Date - -/* - pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) 6)); - pairs.add(new Pair<>(KEY_CONDITION_CODE, (Object) 25)); - pairs.add(new Pair<>(KEY_FORECAST_CONDITION, (Object) 2)); - pairs.add(new Pair<>(KEY_FORECAST_TEMP_HIGH, (Object) 12)); - pairs.add(new Pair<>(KEY_FORECAST_TEMP_LOW, (Object) 0)); -*/ - - byte[] ackMessage = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); - byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); - - //byte[] weatherMessage=encodeTimeStylePebbleWeather(); - - ByteBuffer buf = ByteBuffer.allocate(ackMessage.length + testMessage.length); - - // encode ack and put in front of push message (hack for acknowledging the last message) - buf.put(ackMessage); - buf.put(testMessage); - - return buf.array(); + return iconToLoad; } - private byte[] encodeTimeStylePebbleWeather() { - ArrayList> pairs = new ArrayList<>(); - pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) 6)); - pairs.add(new Pair<>(KEY_CONDITION_CODE, (Object) 1)); - pairs.add(new Pair<>(KEY_FORECAST_CONDITION, (Object) 2)); - pairs.add(new Pair<>(KEY_FORECAST_TEMP_HIGH, (Object) 12)); - pairs.add(new Pair<>(KEY_FORECAST_TEMP_LOW, (Object) 0)); + private byte[] encodeTimeStylePebbleWeather(WeatherSpec weatherSpec) { - byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); - return weatherMessage; + if (weatherSpec == null) { + return null; + } + + ArrayList> pairs = new ArrayList<>(); + boolean isNight = false; //TODO: use the night icons when night + pairs.add(new Pair<>(MESSAGE_KEY_WeatherUseNightIcon, (Object) (isNight ? 1 : 0))); + pairs.add(new Pair<>(MESSAGE_KEY_WeatherTemperature, (Object) (weatherSpec.currentTemp - 273))); + pairs.add(new Pair<>(MESSAGE_KEY_WeatherCondition, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight)))); + pairs.add(new Pair<>(MESSAGE_KEY_WeatherForecastCondition, (Object) (getIconForConditionCode(weatherSpec.tomorrowConditionCode, isNight)))); + pairs.add(new Pair<>(MESSAGE_KEY_WeatherForecastHighTemp, (Object) (weatherSpec.todayMaxTemp - 273))); + + pairs.add(new Pair<>(MESSAGE_KEY_WeatherForecastLowTemp, (Object) (weatherSpec.todayMinTemp - 273))); + + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); } @Override public GBDeviceEvent[] handleMessage(ArrayList> pairs) { - return null; - /* - GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); - sendBytes.encodedBytes = encodeTimeStylePebbleConfig(); - return new GBDeviceEvent[]{sendBytes}; - */ + // Just ACK + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); + return new GBDeviceEvent[]{sendBytesAck}; } -} + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeTimeStylePebbleWeather(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeTimeStylePebbleWeather(weatherSpec); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java new file mode 100644 index 000000000..b3cfce66e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java @@ -0,0 +1,85 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import android.util.Pair; + +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; + +class AppMessageHandlerTrekVolle extends AppMessageHandler { + private static final int MESSAGE_KEY_WEATHER_TEMPERATURE = 10000; + private static final int MESSAGE_KEY_WEATHER_CONDITIONS = 10001; + private static final int MESSAGE_KEY_WEATHER_ICON = 10002; + private static final int MESSAGE_KEY_WEATHER_TEMPERATURE_MIN = 10024; + private static final int MESSAGE_KEY_WEATHER_TEMPERATURE_MAX = 10025; + private static final int MESSAGE_KEY_WEATHER_LOCATION = 10030; + + AppMessageHandlerTrekVolle(UUID uuid, PebbleProtocol pebbleProtocol) { + super(uuid, pebbleProtocol); + } + + private int getIconForConditionCode(int conditionCode, boolean isNight) { + /* + case 1: return RESOURCE_ID_IMAGE_WEATHER_CLEARNIGHT; + case 2: return RESOURCE_ID_IMAGE_WEATHER_CLEAR; + case 3: return RESOURCE_ID_IMAGE_WEATHER_CLOUDYNIGHT; + case 4: return RESOURCE_ID_IMAGE_WEATHER_CLOUDY; + case 5: return RESOURCE_ID_IMAGE_WEATHER_CLOUDS; + case 6: return RESOURCE_ID_IMAGE_WEATHER_THICKCLOUDS; + case 7: return RESOURCE_ID_IMAGE_WEATHER_RAIN; + case 8: return RESOURCE_ID_IMAGE_WEATHER_RAINYNIGHT; + case 9: return RESOURCE_ID_IMAGE_WEATHER_RAINY; + case 10: return RESOURCE_ID_IMAGE_WEATHER_LIGHTNING; + case 11: return RESOURCE_ID_IMAGE_WEATHER_SNOW; + case 12: return RESOURCE_ID_IMAGE_WEATHER_MIST; + */ + return 2; + } + + private byte[] encodeTrekVolleWeather(WeatherSpec weatherSpec) { + + if (weatherSpec == null) { + return null; + } + + boolean isNight = false; // FIXME + ArrayList> pairs = new ArrayList<>(); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273))); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_CONDITIONS, (Object) (weatherSpec.currentCondition))); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode, isNight)))); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE_MAX, (Object) (weatherSpec.todayMaxTemp - 273))); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_TEMPERATURE_MIN, (Object) (weatherSpec.todayMinTemp - 273))); + pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_LOCATION, (Object) weatherSpec.location)); + + + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + } + + @Override + public GBDeviceEvent[] handleMessage(ArrayList> pairs) { + // Just ACK + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); + return new GBDeviceEvent[]{sendBytesAck}; + } + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeTrekVolleWeather(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeTrekVolleWeather(weatherSpec); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerWeatherNeat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerWeatherNeat.java deleted file mode 100644 index c32074d32..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerWeatherNeat.java +++ /dev/null @@ -1,54 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; - -import android.util.Pair; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.UUID; - -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; -import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; - -public class AppMessageHandlerWeatherNeat extends AppMessageHandler { - - public static final int KEY_REQUEST = 0; - public static final int KEY_CITY = 1; - public static final int KEY_TEMPERATUR = 2; - public static final int KEY_CONDITION = 3; - public static final int KEY_LIGHT_TIME = 5; - - private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerWeatherNeat.class); - - public AppMessageHandlerWeatherNeat(UUID uuid, PebbleProtocol pebbleProtocol) { - super(uuid, pebbleProtocol); - } - - private byte[] encodeWeatherNeatMessage(String city, String temperature, String condition, int light_time) { - ArrayList> pairs = new ArrayList<>(4); - pairs.add(new Pair<>(1, (Object) city)); - pairs.add(new Pair<>(2, (Object) temperature)); - pairs.add(new Pair<>(3, (Object) condition)); - pairs.add(new Pair<>(5, (Object) light_time)); // seconds for backlight on shake - - byte[] ackMessage = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id); - byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); - - ByteBuffer buf = ByteBuffer.allocate(ackMessage.length + testMessage.length); - - // encode ack and put in front of push message (hack for acknowledging the last message) - buf.put(ackMessage); - buf.put(testMessage); - - return buf.array(); - } - - @Override - public GBDeviceEvent[] handleMessage(ArrayList> pairs) { - GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); - sendBytes.encodedBytes = encodeWeatherNeatMessage("Berlin", "22 C", "cloudy", 0); - return new GBDeviceEvent[]{sendBytes}; - } -} 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 4bcc6ae55..1f69dcd1c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java @@ -16,7 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivitySample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.GB; -public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth { +class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class); 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 70ece5f29..3b56e17fa 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 @@ -618,6 +618,10 @@ class PebbleIoThread extends GBDeviceIoThread { write(mPebbleProtocol.encodeActivateHRM(true)); return; } + if (uri.equals(Uri.parse("fake://weather"))) { + write(mPebbleProtocol.encodeActivateWeather(true)); + return; + } if (mIsInstalling) { return; @@ -628,7 +632,10 @@ class PebbleIoThread extends GBDeviceIoThread { try { mPBWReader = new PBWReader(uri, getContext(), platformName); } catch (FileNotFoundException e) { - LOG.warn("file not found!"); + LOG.warn("file not found: " + e.getMessage(), e); + return; + } catch (IOException e) { + LOG.warn("unable to read file: " + e.getMessage(), e); return; } 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 065b7ade4..625cc7340 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 @@ -38,144 +38,145 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; public class PebbleProtocol extends GBDeviceProtocol { private static final Logger LOG = LoggerFactory.getLogger(PebbleProtocol.class); - static final short ENDPOINT_TIME = 11; - static final short ENDPOINT_FIRMWAREVERSION = 16; - public static final short ENDPOINT_PHONEVERSION = 17; - static final short ENDPOINT_SYSTEMMESSAGE = 18; - static final short ENDPOINT_MUSICCONTROL = 32; - static final short ENDPOINT_PHONECONTROL = 33; + private static final short ENDPOINT_TIME = 11; + private static final short ENDPOINT_FIRMWAREVERSION = 16; + private static final short ENDPOINT_PHONEVERSION = 17; + private static final short ENDPOINT_SYSTEMMESSAGE = 18; + private static final short ENDPOINT_MUSICCONTROL = 32; + private static final short ENDPOINT_PHONECONTROL = 33; static final short ENDPOINT_APPLICATIONMESSAGE = 48; - static final short ENDPOINT_LAUNCHER = 49; - static final short ENDPOINT_APPRUNSTATE = 52; // 3.x only - static final short ENDPOINT_LOGS = 2000; - static final short ENDPOINT_PING = 2001; - static final short ENDPOINT_LOGDUMP = 2002; - static final short ENDPOINT_RESET = 2003; - static final short ENDPOINT_APP = 2004; - static final short ENDPOINT_APPLOGS = 2006; - static final short ENDPOINT_NOTIFICATION = 3000; - static final short ENDPOINT_EXTENSIBLENOTIFS = 3010; - static final short ENDPOINT_RESOURCE = 4000; - static final short ENDPOINT_SYSREG = 5000; - static final short ENDPOINT_FCTREG = 5001; - static final short ENDPOINT_APPMANAGER = 6000; - static final short ENDPOINT_APPFETCH = 6001; // 3.x only - public static final short ENDPOINT_DATALOG = 6778; - static final short ENDPOINT_RUNKEEPER = 7000; - static final short ENDPOINT_SCREENSHOT = 8000; - static final short ENDPOINT_AUDIOSTREAM = 10000; - static final short ENDPOINT_VOICECONTROL = 11000; - static final short ENDPOINT_NOTIFICATIONACTION = 11440; // 3.x only, TODO: find a better name - static final short ENDPOINT_APPREORDER = (short) 0xabcd; // 3.x only - static final short ENDPOINT_BLOBDB = (short) 45531; // 3.x only - static final short ENDPOINT_PUTBYTES = (short) 48879; + private static final short ENDPOINT_LAUNCHER = 49; + private static final short ENDPOINT_APPRUNSTATE = 52; // FW >=3.x + private static final short ENDPOINT_LOGS = 2000; + private static final short ENDPOINT_PING = 2001; + private static final short ENDPOINT_LOGDUMP = 2002; + private static final short ENDPOINT_RESET = 2003; + private static final short ENDPOINT_APP = 2004; + private static final short ENDPOINT_APPLOGS = 2006; + private static final short ENDPOINT_NOTIFICATION = 3000; // FW 1.x-2-x + private static final short ENDPOINT_EXTENSIBLENOTIFS = 3010; // FW 2.x + private static final short ENDPOINT_RESOURCE = 4000; + private static final short ENDPOINT_SYSREG = 5000; + private static final short ENDPOINT_FCTREG = 5001; + private static final short ENDPOINT_APPMANAGER = 6000; + private static final short ENDPOINT_APPFETCH = 6001; // FW >=3.x + private static final short ENDPOINT_DATALOG = 6778; + private static final short ENDPOINT_RUNKEEPER = 7000; + private static final short ENDPOINT_SCREENSHOT = 8000; + private static final short ENDPOINT_AUDIOSTREAM = 10000; + private static final short ENDPOINT_VOICECONTROL = 11000; + private static final short ENDPOINT_NOTIFICATIONACTION = 11440; // FW >=3.x, TODO: find a better name + private static final short ENDPOINT_APPREORDER = (short) 0xabcd; // FW >=3.x + private static final short ENDPOINT_BLOBDB = (short) 0xb1db; // FW >=3.x + private static final short ENDPOINT_PUTBYTES = (short) 0xbeef; - static final byte APPRUNSTATE_START = 1; - static final byte APPRUNSTATE_STOP = 2; + private static final byte APPRUNSTATE_START = 1; + private static final byte APPRUNSTATE_STOP = 2; - static final byte BLOBDB_INSERT = 1; - static final byte BLOBDB_DELETE = 4; - static final byte BLOBDB_CLEAR = 5; + private static final byte BLOBDB_INSERT = 1; + private static final byte BLOBDB_DELETE = 4; + private static final byte BLOBDB_CLEAR = 5; - static final byte BLOBDB_PIN = 1; - static final byte BLOBDB_APP = 2; - static final byte BLOBDB_REMINDER = 3; - static final byte BLOBDB_NOTIFICATION = 4; - static final byte BLOBDB_CANNED_MESSAGES = 6; - static final byte BLOBDB_PREFERENCES = 7; - static final byte BLOBDB_APPGLANCE = 11; + private static final byte BLOBDB_PIN = 1; + private static final byte BLOBDB_APP = 2; + private static final byte BLOBDB_REMINDER = 3; + private static final byte BLOBDB_NOTIFICATION = 4; + private static final byte BLOBDB_WEATHER = 5; + private static final byte BLOBDB_CANNED_MESSAGES = 6; + private static final byte BLOBDB_PREFERENCES = 7; + private static final byte BLOBDB_APPSETTINGS = 9; + private static final byte BLOBDB_APPGLANCE = 11; - static final byte BLOBDB_SUCCESS = 1; - static final byte BLOBDB_GENERALFAILURE = 2; - static final byte BLOBDB_INVALIDOPERATION = 3; - static final byte BLOBDB_INVALIDDATABASEID = 4; - static final byte BLOBDB_INVALIDDATA = 5; - static final byte BLOBDB_KEYDOESNOTEXIST = 6; - static final byte BLOBDB_DATABASEFULL = 7; - static final byte BLOBDB_DATASTALE = 8; + private static final byte BLOBDB_SUCCESS = 1; + private static final byte BLOBDB_GENERALFAILURE = 2; + private static final byte BLOBDB_INVALIDOPERATION = 3; + private static final byte BLOBDB_INVALIDDATABASEID = 4; + private static final byte BLOBDB_INVALIDDATA = 5; + private static final byte BLOBDB_KEYDOESNOTEXIST = 6; + private static final byte BLOBDB_DATABASEFULL = 7; + private static final byte BLOBDB_DATASTALE = 8; - // This is not in the Pebble protocol - static final byte NOTIFICATION_UNDEFINED = -1; + private static final byte NOTIFICATION_EMAIL = 0; + private static final byte NOTIFICATION_SMS = 1; + private static final byte NOTIFICATION_TWITTER = 2; + private static final byte NOTIFICATION_FACEBOOK = 3; - static final byte NOTIFICATION_EMAIL = 0; - static final byte NOTIFICATION_SMS = 1; - static final byte NOTIFICATION_TWITTER = 2; - static final byte NOTIFICATION_FACEBOOK = 3; + private static final byte PHONECONTROL_ANSWER = 1; + private static final byte PHONECONTROL_HANGUP = 2; + private static final byte PHONECONTROL_GETSTATE = 3; + private static final byte PHONECONTROL_INCOMINGCALL = 4; + private static final byte PHONECONTROL_OUTGOINGCALL = 5; + private static final byte PHONECONTROL_MISSEDCALL = 6; + private static final byte PHONECONTROL_RING = 7; + private static final byte PHONECONTROL_START = 8; + private static final byte PHONECONTROL_END = 9; - static final byte PHONECONTROL_ANSWER = 1; - static final byte PHONECONTROL_HANGUP = 2; - static final byte PHONECONTROL_GETSTATE = 3; - static final byte PHONECONTROL_INCOMINGCALL = 4; - static final byte PHONECONTROL_OUTGOINGCALL = 5; - static final byte PHONECONTROL_MISSEDCALL = 6; - static final byte PHONECONTROL_RING = 7; - static final byte PHONECONTROL_START = 8; - static final byte PHONECONTROL_END = 9; + private static final byte MUSICCONTROL_SETMUSICINFO = 0x10; + private static final byte MUSICCONTROL_SETPLAYSTATE = 0x11; - static final byte MUSICCONTROL_SETMUSICINFO = 0x10; - static final byte MUSICCONTROL_SETPLAYSTATE = 0x11; + private static final byte MUSICCONTROL_PLAYPAUSE = 1; + private static final byte MUSICCONTROL_PAUSE = 2; + private static final byte MUSICCONTROL_PLAY = 3; + private static final byte MUSICCONTROL_NEXT = 4; + private static final byte MUSICCONTROL_PREVIOUS = 5; + private static final byte MUSICCONTROL_VOLUMEUP = 6; + private static final byte MUSICCONTROL_VOLUMEDOWN = 7; + private static final byte MUSICCONTROL_GETNOWPLAYING = 8; - static final byte MUSICCONTROL_PLAYPAUSE = 1; - static final byte MUSICCONTROL_PAUSE = 2; - static final byte MUSICCONTROL_PLAY = 3; - static final byte MUSICCONTROL_NEXT = 4; - static final byte MUSICCONTROL_PREVIOUS = 5; - static final byte MUSICCONTROL_VOLUMEUP = 6; - static final byte MUSICCONTROL_VOLUMEDOWN = 7; - static final byte MUSICCONTROL_GETNOWPLAYING = 8; + private static final byte MUSICCONTROL_STATE_PAUSED = 0x00; + private static final byte MUSICCONTROL_STATE_PLAYING = 0x01; + private static final byte MUSICCONTROL_STATE_REWINDING = 0x02; + private static final byte MUSICCONTROL_STATE_FASTWORWARDING = 0x03; + private static final byte MUSICCONTROL_STATE_UNKNOWN = 0x04; - static final byte MUSICCONTROL_STATE_PAUSED = 0x00; - static final byte MUSICCONTROL_STATE_PLAYING = 0x01; - static final byte MUSICCONTROL_STATE_REWINDING = 0x02; - static final byte MUSICCONTROL_STATE_FASTWORWARDING = 0x03; - static final byte MUSICCONTROL_STATE_UNKNOWN = 0x04; + private static final byte NOTIFICATIONACTION_ACK = 0; + private static final byte NOTIFICATIONACTION_NACK = 1; + private static final byte NOTIFICATIONACTION_INVOKE = 0x02; + private static final byte NOTIFICATIONACTION_RESPONSE = 0x11; - static final byte NOTIFICATIONACTION_ACK = 0; - static final byte NOTIFICATIONACTION_NACK = 1; - static final byte NOTIFICATIONACTION_INVOKE = 0x02; - static final byte NOTIFICATIONACTION_RESPONSE = 0x11; + private static final byte TIME_GETTIME = 0; + private static final byte TIME_SETTIME = 2; + private static final byte TIME_SETTIME_UTC = 3; - static final byte TIME_GETTIME = 0; - static final byte TIME_SETTIME = 2; - static final byte TIME_SETTIME_UTC = 3; + private static final byte FIRMWAREVERSION_GETVERSION = 0; - static final byte FIRMWAREVERSION_GETVERSION = 0; + private static final byte APPMANAGER_GETAPPBANKSTATUS = 1; + private static final byte APPMANAGER_REMOVEAPP = 2; + private static final byte APPMANAGER_REFRESHAPP = 3; + private static final byte APPMANAGER_GETUUIDS = 5; - static final byte APPMANAGER_GETAPPBANKSTATUS = 1; - static final byte APPMANAGER_REMOVEAPP = 2; - static final byte APPMANAGER_REFRESHAPP = 3; - static final byte APPMANAGER_GETUUIDS = 5; + private static final int APPMANAGER_RES_SUCCESS = 1; - static final int APPMANAGER_RES_SUCCESS = 1; + private static final byte APPLICATIONMESSAGE_PUSH = 1; + private static final byte APPLICATIONMESSAGE_REQUEST = 2; + private static final byte APPLICATIONMESSAGE_ACK = (byte) 0xff; + private static final byte APPLICATIONMESSAGE_NACK = (byte) 0x7f; - static final byte APPLICATIONMESSAGE_PUSH = 1; - static final byte APPLICATIONMESSAGE_REQUEST = 2; - static final byte APPLICATIONMESSAGE_ACK = (byte) 0xff; - static final byte APPLICATIONMESSAGE_NACK = (byte) 0x7f; + private static final byte DATALOG_OPENSESSION = 0x01; + private static final byte DATALOG_SENDDATA = 0x02; + private static final byte DATALOG_CLOSE = 0x03; + private static final byte DATALOG_TIMEOUT = 0x07; + private static final byte DATALOG_REPORTSESSIONS = (byte) 0x84; + private static final byte DATALOG_ACK = (byte) 0x85; + private static final byte DATALOG_NACK = (byte) 0x86; - static final byte DATALOG_OPENSESSION = 0x01; - static final byte DATALOG_SENDDATA = 0x02; - static final byte DATALOG_CLOSE = 0x03; - static final byte DATALOG_TIMEOUT = 0x07; - static final byte DATALOG_REPORTSESSIONS = (byte) 0x84; - static final byte DATALOG_ACK = (byte) 0x85; - static final byte DATALOG_NACK = (byte) 0x86; + private static final byte PING_PING = 0; + private static final byte PING_PONG = 1; - static final byte PING_PING = 0; - static final byte PING_PONG = 1; - - static final byte PUTBYTES_INIT = 1; - static final byte PUTBYTES_SEND = 2; - static final byte PUTBYTES_COMMIT = 3; - static final byte PUTBYTES_ABORT = 4; - static final byte PUTBYTES_COMPLETE = 5; + private static final byte PUTBYTES_INIT = 1; + private static final byte PUTBYTES_SEND = 2; + private static final byte PUTBYTES_COMMIT = 3; + private static final byte PUTBYTES_ABORT = 4; + private static final byte PUTBYTES_COMPLETE = 5; public static final byte PUTBYTES_TYPE_FIRMWARE = 1; public static final byte PUTBYTES_TYPE_RECOVERY = 2; @@ -185,70 +186,54 @@ public class PebbleProtocol extends GBDeviceProtocol { public static final byte PUTBYTES_TYPE_FILE = 6; public static final byte PUTBYTES_TYPE_WORKER = 7; - static final byte RESET_REBOOT = 0; + private static final byte RESET_REBOOT = 0; - static final byte SCREENSHOT_TAKE = 0; + private static final byte SCREENSHOT_TAKE = 0; - static final byte SYSTEMMESSAGE_NEWFIRMWAREAVAILABLE = 0; - static final byte SYSTEMMESSAGE_FIRMWARESTART = 1; - static final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2; - static final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3; - static final byte SYSTEMMESSAGE_FIRMWARE_UPTODATE = 4; - static final byte SYSTEMMESSAGE_FIRMWARE_OUTOFDATE = 5; - static final byte SYSTEMMESSAGE_STOPRECONNECTING = 6; - static final byte SYSTEMMESSAGE_STARTRECONNECTING = 7; + private static final byte SYSTEMMESSAGE_NEWFIRMWAREAVAILABLE = 0; + private static final byte SYSTEMMESSAGE_FIRMWARESTART = 1; + private static final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2; + private static final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3; + private static final byte SYSTEMMESSAGE_FIRMWARE_UPTODATE = 4; + private static final byte SYSTEMMESSAGE_FIRMWARE_OUTOFDATE = 5; + private static final byte SYSTEMMESSAGE_STOPRECONNECTING = 6; + private static final byte SYSTEMMESSAGE_STARTRECONNECTING = 7; - static final byte PHONEVERSION_REQUEST = 0; - static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains - static final byte PHONEVERSION_APPVERSION_MAJOR = 2; - static final byte PHONEVERSION_APPVERSION_MINOR = 3; - static final byte PHONEVERSION_APPVERSION_PATCH = 0; + private static final byte PHONEVERSION_REQUEST = 0; + private static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains + private static final byte PHONEVERSION_APPVERSION_MAJOR = 2; + private static final byte PHONEVERSION_APPVERSION_MINOR = 3; + private static final byte PHONEVERSION_APPVERSION_PATCH = 0; - static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000; + private static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000; - static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010; - static final int PHONEVERSION_REMOTE_CAPS_SMS = 0x00000020; - static final int PHONEVERSION_REMOTE_CAPS_GPS = 0x00000040; - static final int PHONEVERSION_REMOTE_CAPS_BTLE = 0x00000080; - static final int PHONEVERSION_REMOTE_CAPS_REARCAMERA = 0x00000100; - static final int PHONEVERSION_REMOTE_CAPS_ACCEL = 0x00000200; - static final int PHONEVERSION_REMOTE_CAPS_GYRO = 0x00000400; - static final int PHONEVERSION_REMOTE_CAPS_COMPASS = 0x00000800; + private static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010; + private static final int PHONEVERSION_REMOTE_CAPS_SMS = 0x00000020; + private static final int PHONEVERSION_REMOTE_CAPS_GPS = 0x00000040; + private static final int PHONEVERSION_REMOTE_CAPS_BTLE = 0x00000080; + private static final int PHONEVERSION_REMOTE_CAPS_REARCAMERA = 0x00000100; + private static final int PHONEVERSION_REMOTE_CAPS_ACCEL = 0x00000200; + private static final int PHONEVERSION_REMOTE_CAPS_GYRO = 0x00000400; + private static final int PHONEVERSION_REMOTE_CAPS_COMPASS = 0x00000800; - static final byte PHONEVERSION_REMOTE_OS_UNKNOWN = 0; - static final byte PHONEVERSION_REMOTE_OS_IOS = 1; - static final byte PHONEVERSION_REMOTE_OS_ANDROID = 2; - static final byte PHONEVERSION_REMOTE_OS_OSX = 3; - static final byte PHONEVERSION_REMOTE_OS_LINUX = 4; - static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5; + private static final byte PHONEVERSION_REMOTE_OS_UNKNOWN = 0; + private static final byte PHONEVERSION_REMOTE_OS_IOS = 1; + private static final byte PHONEVERSION_REMOTE_OS_ANDROID = 2; + private static final byte PHONEVERSION_REMOTE_OS_OSX = 3; + private static final byte PHONEVERSION_REMOTE_OS_LINUX = 4; + private static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5; - static final byte TYPE_BYTEARRAY = 0; - static final byte TYPE_CSTRING = 1; - static final byte TYPE_UINT = 2; - static final byte TYPE_INT = 3; + private static final byte TYPE_BYTEARRAY = 0; + private static final byte TYPE_CSTRING = 1; + private static final byte TYPE_UINT = 2; + private static final byte TYPE_INT = 3; - static final short LENGTH_PREFIX = 4; - static final short LENGTH_SIMPLEMESSAGE = 1; + private final short LENGTH_PREFIX = 4; - static final short LENGTH_APPFETCH = 2; - static final short LENGTH_APPRUNSTATE = 17; - static final short LENGTH_PING = 5; - static final short LENGTH_PHONEVERSION = 17; - static final short LENGTH_REMOVEAPP_2X = 17; - static final short LENGTH_REFRESHAPP = 5; - static final short LENGTH_SETTIME = 5; - static final short LENGTH_SYSTEMMESSAGE = 2; - static final short LENGTH_UPLOADSTART_2X = 7; - static final short LENGTH_UPLOADSTART_3X = 10; - static final short LENGTH_UPLOADCHUNK = 9; - static final short LENGTH_UPLOADCOMMIT = 9; - static final short LENGTH_UPLOADCOMPLETE = 5; - static final short LENGTH_UPLOADCANCEL = 5; + private static final byte LENGTH_UUID = 16; - static final byte LENGTH_UUID = 16; - - static final long GB_UUID_MASK = 0x4767744272646700L; + private static final long GB_UUID_MASK = 0x4767744272646700L; // base is -8 private static final String[] hwRevisions = { @@ -268,18 +253,18 @@ public class PebbleProtocol extends GBDeviceProtocol { private static final Random mRandom = new Random(); int mFwMajor = 3; - boolean mForceProtocol = false; - GBDeviceEventScreenshot mDevEventScreenshot = null; - int mScreenshotRemaining = -1; + private boolean mForceProtocol = false; + private GBDeviceEventScreenshot mDevEventScreenshot = null; + private int mScreenshotRemaining = -1; //monochrome black + white - static final byte[] clut_pebble = { + private static final byte[] clut_pebble = { 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 }; // linear BGR222 (6 bit, 64 entries) - static final byte[] clut_pebbletime = new byte[]{ + private static final byte[] clut_pebbletime = new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, (byte) 0xaa, 0x00, 0x00, 0x00, @@ -368,28 +353,39 @@ public class PebbleProtocol extends GBDeviceProtocol { public static final UUID UUID_PEBBLE_HEALTH = UUID.fromString("36d8c6ed-4c83-4fa1-a9e2-8f12dc941f8c"); // FIXME: store somewhere else, this is also accessed by other code public static final UUID UUID_WORKOUT = UUID.fromString("fef82c82-7176-4e22-88de-35a3fc18d43f"); // FIXME: store somewhere else, this is also accessed by other code + public static final UUID UUID_WEATHER = UUID.fromString("61b22bc8-1e29-460d-a236-3fe409a439ff"); // FIXME: store somewhere else, this is also accessed by other code private static final UUID UUID_GBPEBBLE = UUID.fromString("61476764-7465-7262-6469-656775527a6c"); private static final UUID UUID_MORPHEUZ = UUID.fromString("5be44f1d-d262-4ea6-aa30-ddbec1e3cab2"); - private static final UUID UUID_WHETHERNEAT = UUID.fromString("3684003b-a685-45f9-a713-abc6364ba051"); private static final UUID UUID_MISFIT = UUID.fromString("0b73b76a-cd65-4dc2-9585-aaa213320858"); private static final UUID UUID_PEBBLE_TIMESTYLE = UUID.fromString("4368ffa4-f0fb-4823-90be-f754b076bdaa"); private static final UUID UUID_PEBSTYLE = UUID.fromString("da05e84d-e2a2-4020-a2dc-9cdcf265fcdd"); + private static final UUID UUID_MARIOTIME = UUID.fromString("43caa750-2896-4f46-94dc-1adbd4bc1ff3"); + private static final UUID UUID_HELTHIFY = UUID.fromString("7ee97b2c-95e8-4720-b94e-70fccd905d98"); + private static final UUID UUID_TREKVOLLE = UUID.fromString("2da02267-7a19-4e49-9ed1-439d25db14e4"); + private static final UUID UUID_ZERO = new UUID(0, 0); + private static final UUID UUID_LOCATION = UUID.fromString("2c7e6a86-51e5-4ddd-b606-db43d1e4ad28"); // might be the location of "Berlin" or "Auto" + private final Map mAppMessageHandlers = new HashMap<>(); + private UUID currentRunningApp = UUID_ZERO; + public PebbleProtocol(GBDevice device) { super(device); mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_WHETHERNEAT, new AppMessageHandlerWeatherNeat(UUID_WHETHERNEAT, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_PEBBLE_TIMESTYLE, new AppMessageHandlerTimeStylePebble(UUID_PEBBLE_TIMESTYLE, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this)); + //mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this)); + mAppMessageHandlers.put(UUID_MARIOTIME, new AppMessageHandlerMarioTime(UUID_MARIOTIME, PebbleProtocol.this)); + mAppMessageHandlers.put(UUID_HELTHIFY, new AppMessageHandlerHealthify(UUID_HELTHIFY, PebbleProtocol.this)); + mAppMessageHandlers.put(UUID_TREKVOLLE, new AppMessageHandlerTrekVolle(UUID_TREKVOLLE, PebbleProtocol.this)); } private final HashMap mDatalogSessions = new HashMap<>(); private byte[] encodeSimpleMessage(short endpoint, byte command) { + final short LENGTH_SIMPLEMESSAGE = 1; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_SIMPLEMESSAGE); @@ -502,6 +498,7 @@ public class PebbleProtocol extends GBDeviceProtocol { @Override public byte[] encodeSetTime() { + final short LENGTH_SETTIME = 5; long ts = System.currentTimeMillis(); long ts_offset = (SimpleTimeZone.getDefault().getOffset(ts)); ByteBuffer buf; @@ -532,6 +529,13 @@ public class PebbleProtocol extends GBDeviceProtocol { @Override public byte[] encodeFindDevice(boolean start) { return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END); + /* + int ts = (int) (System.currentTimeMillis() / 1000); + + if (start) { + //return encodeWeatherPin(ts, "Weather", "1°/-1°", "Gadgetbridge is Sunny", "Berlin", 37); + } + */ } private byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) { @@ -762,6 +766,21 @@ public class PebbleProtocol extends GBDeviceProtocol { activate ? new byte[]{0x01} : new byte[]{0x00}); } + byte[] encodeActivateWeather(boolean activate) { + if (activate) { + ByteBuffer buf = ByteBuffer.allocate(0x61); + buf.put((byte) 1); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putLong(UUID_LOCATION.getMostSignificantBits()); + buf.putLong(UUID_LOCATION.getLeastSignificantBits()); + // disable remaining 5 possible location + buf.put(new byte[60 - LENGTH_UUID]); + return encodeBlobdb("weatherApp", BLOBDB_INSERT, BLOBDB_APPSETTINGS, buf.array()); + } else { + return encodeBlobdb("weatherApp", BLOBDB_DELETE, BLOBDB_APPSETTINGS, null); + } + } + byte[] encodeReportDataLogSessions() { return encodeSimpleMessage(ENDPOINT_DATALOG, DATALOG_REPORTSESSIONS); } @@ -1002,6 +1021,178 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + private byte[] encodeWeatherPin(int timestamp, String title, String subtitle, String body, String location, int iconId) { + final short NOTIFICATION_PIN_LENGTH = 46; + final short ACTION_LENGTH_MIN = 10; + + String[] parts = {title, subtitle, body, location, "test", "test"}; + + // Calculate length first + byte actions_count = 1; + short actions_length; + String remove_string = "Remove"; + actions_length = (short) (ACTION_LENGTH_MIN * actions_count + remove_string.getBytes().length); + + byte attributes_count = 3; + short attributes_length = (short) (21 + actions_length); + if (parts != null) { + for (String s : parts) { + if (s == null || s.equals("")) { + continue; + } + attributes_count++; + attributes_length += (3 + s.getBytes().length); + } + } + + UUID uuid = UUID.fromString("61b22bc8-1e29-460d-a236-3fe409a43901"); + + short pin_length = (short) (NOTIFICATION_PIN_LENGTH + attributes_length); + + ByteBuffer buf = ByteBuffer.allocate(pin_length); + + // pin (46 bytes) + buf.order(ByteOrder.BIG_ENDIAN); + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits()); + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits() | 0xff); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(timestamp); // 32-bit timestamp + buf.putShort((short) 0); // duration + buf.put((byte) 0x02); // type (0x02 = pin) + buf.putShort((short) 0x0001); // flags 0x0001 = ? + buf.put((byte) 0x06); // layout (0x06 = weather) + buf.putShort(attributes_length); // total length of all attributes and actions in bytes + buf.put(attributes_count); + buf.put(actions_count); + + byte attribute_id = 0; + // Encode Pascal-Style Strings + if (parts != null) { + for (String s : parts) { + attribute_id++; + if (s == null || s.equals("")) { + continue; + } + + int partlength = s.getBytes().length; + if (partlength > 512) partlength = 512; + if (attribute_id == 4) { + buf.put((byte) 11); + } else if (attribute_id == 5) { + buf.put((byte) 25); + } else if (attribute_id == 6) { + buf.put((byte) 26); + } else { + buf.put(attribute_id); + } + buf.putShort((short) partlength); + buf.put(s.getBytes(), 0, partlength); + } + } + + buf.put((byte) 4); // icon + buf.putShort((short) 4); // length of int + buf.putInt(0x80000000 | iconId); + + buf.put((byte) 6); // icon + buf.putShort((short) 4); // length of int + buf.putInt(0x80000000 | iconId); + + buf.put((byte) 14); // last updated + buf.putShort((short) 4); // length of int + buf.putInt(timestamp); + + // remove action + buf.put((byte) 123); // action id + buf.put((byte) 0x09); // remove + buf.put((byte) 0x01); // number attributes + buf.put((byte) 0x01); // attribute id (title) + buf.putShort((short) remove_string.getBytes().length); + buf.put(remove_string.getBytes()); + + return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array()); + } + + + @Override + public byte[] encodeSendWeather(WeatherSpec weatherSpec) { + byte[] forecastProtocol = null; + byte[] watchfaceProtocol = null; + int length = 0; + if (mFwMajor >= 4) { + forecastProtocol = encodeWeatherForecast(weatherSpec); + length += forecastProtocol.length; + } + AppMessageHandler handler = mAppMessageHandlers.get(currentRunningApp); + if (handler != null) { + watchfaceProtocol = handler.encodeUpdateWeather(weatherSpec); + if (watchfaceProtocol != null) { + length += watchfaceProtocol.length; + } + } + ByteBuffer buf = ByteBuffer.allocate(length); + + if (forecastProtocol != null) { + buf.put(forecastProtocol); + } + if (watchfaceProtocol != null) { + buf.put(watchfaceProtocol); + } + + return buf.array(); + } + + private byte[] encodeWeatherForecast(WeatherSpec weatherSpec) { + final short WEATHER_FORECAST_LENGTH = 20; + + String[] parts = {weatherSpec.location, weatherSpec.currentCondition}; + + // Calculate length first + short attributes_length = 0; + if (parts != null) { + for (String s : parts) { + if (s == null || s.equals("")) { + continue; + } + attributes_length += (2 + s.getBytes().length); + } + } + + short pin_length = (short) (WEATHER_FORECAST_LENGTH + attributes_length); + + ByteBuffer buf = ByteBuffer.allocate(pin_length); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.put((byte) 3); // unknown, always 3? + buf.putShort((short) (weatherSpec.currentTemp - 273)); + buf.put(Weather.mapToPebbleCondition(weatherSpec.currentConditionCode)); + buf.putShort((short) (weatherSpec.todayMaxTemp - 273)); + buf.putShort((short) (weatherSpec.todayMinTemp - 273)); + buf.put(Weather.mapToPebbleCondition(weatherSpec.tomorrowConditionCode)); + buf.putShort((short) (weatherSpec.tomorrowMaxTemp - 273)); + buf.putShort((short) (weatherSpec.tomorrowMinTemp - 273)); + buf.putInt(weatherSpec.timestamp); + buf.put((byte) 0); // automatic location 0=manual 1=auto + buf.putShort(attributes_length); + + // Encode Pascal-Style Strings + if (parts != null) { + for (String s : parts) { + if (s == null || s.equals("")) { + continue; + } + + int partlength = s.getBytes().length; + if (partlength > 512) partlength = 512; + buf.putShort((short) partlength); + buf.put(s.getBytes(), 0, partlength); + } + } + + return encodeBlobdb(UUID_LOCATION, BLOBDB_INSERT, BLOBDB_WEATHER, buf.array()); + } + private byte[] encodeActionResponse(UUID uuid, int iconId, String caption) { short length = (short) (29 + caption.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); @@ -1045,7 +1236,8 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_APP, buf.array()); } - public byte[] encodeAppFetchAck() { + byte[] encodeAppFetchAck() { + final short LENGTH_APPFETCH = 2; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPFETCH); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_APPFETCH); @@ -1194,6 +1386,7 @@ public class PebbleProtocol extends GBDeviceProtocol { @Override public byte[] encodeAppStart(UUID uuid, boolean start) { if (mFwMajor >= 3) { + final short LENGTH_APPRUNSTATE = 17; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_APPRUNSTATE); @@ -1219,8 +1412,12 @@ public class PebbleProtocol extends GBDeviceProtocol { if (UUID_WORKOUT.equals(uuid)) { return encodeActivateHRM(false); } + if (UUID_WEATHER.equals(uuid)) { //TODO: probably it wasn't present in firmware 3 + return encodeActivateWeather(false); + } return encodeBlobdb(uuid, BLOBDB_DELETE, BLOBDB_APP, null); } else { + final short LENGTH_REMOVEAPP_2X = 17; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REMOVEAPP_2X); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_REMOVEAPP_2X); @@ -1233,6 +1430,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } private byte[] encodePhoneVersion2x(byte os) { + final short LENGTH_PHONEVERSION = 17; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_PHONEVERSION); @@ -1359,10 +1557,10 @@ public class PebbleProtocol extends GBDeviceProtocol { byte[] encodeUploadStart(byte type, int app_id, int size, String filename) { short length; if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) { - length = LENGTH_UPLOADSTART_3X; + length = (short) 10; type |= 0b10000000; } else { - length = LENGTH_UPLOADSTART_2X; + length = (short) 7; } if (type == PUTBYTES_TYPE_FILE && filename != null) { @@ -1393,6 +1591,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte[] encodeUploadChunk(int token, byte[] buffer, int size) { + final short LENGTH_UPLOADCHUNK = 9; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) (LENGTH_UPLOADCHUNK + size)); @@ -1405,6 +1604,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte[] encodeUploadCommit(int token, int crc) { + final short LENGTH_UPLOADCOMMIT = 9; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMMIT); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCOMMIT); @@ -1416,6 +1616,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte[] encodeUploadComplete(int token) { + final short LENGTH_UPLOADCOMPLETE = 5; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCOMPLETE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCOMPLETE); @@ -1426,6 +1627,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte[] encodeUploadCancel(int token) { + final short LENGTH_UPLOADCANCEL = 5; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCANCEL); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_UPLOADCANCEL); @@ -1436,6 +1638,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } private byte[] encodeSystemMessage(byte systemMessage) { + final short LENGTH_SYSTEMMESSAGE = 2; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SYSTEMMESSAGE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_SYSTEMMESSAGE); @@ -1460,6 +1663,7 @@ public class PebbleProtocol extends GBDeviceProtocol { byte[] encodeAppRefresh(int index) { + final short LENGTH_REFRESHAPP = 5; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_REFRESHAPP); @@ -1496,6 +1700,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } private byte[] encodePing(byte command, int cookie) { + final short LENGTH_PING = 5; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PING); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_PING); @@ -1910,10 +2115,10 @@ public class PebbleProtocol extends GBDeviceProtocol { switch (command) { case APPRUNSTATE_START: LOG.info(ENDPOINT_NAME + ": started " + uuid); - + currentRunningApp = uuid; AppMessageHandler handler = mAppMessageHandlers.get(uuid); if (handler != null) { - return handler.pushMessage(); + return handler.onAppStart(); } else { GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement(); @@ -2245,7 +2450,8 @@ public class PebbleProtocol extends GBDeviceProtocol { devEvts = handler.handleMessage(dict); } else { - devEvts = handler.pushMessage(); + currentRunningApp = uuid; + devEvts = handler.onAppStart(); } } else { devEvts = new GBDeviceEvent[]{null}; @@ -2256,6 +2462,7 @@ public class PebbleProtocol extends GBDeviceProtocol { devEvts = decodeDictToJSONAppMessage(uuid, buf); } else { + currentRunningApp = uuid; GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement(); gbDeviceEventAppManagement.uuid = uuid; gbDeviceEventAppManagement.type = GBDeviceEventAppManagement.EventType.START; 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 2b37852a9..f88f5f77e 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 @@ -18,6 +18,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; @@ -170,4 +171,11 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { super.onTestNewFunction(); } } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + if (reconnect()) { + super.onSendWeather(weatherSpec); + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java index c4539a87a..b19b4329d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java @@ -25,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; @@ -280,4 +281,9 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport { public void onTestNewFunction() { } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } } 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 9c8add33c..216f2c517 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 @@ -11,6 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; /** @@ -27,7 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; * to create the device specific message for the respective events and sends them to the device via {@link #sendToDevice(byte[])}. */ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport { - protected GBDeviceProtocol gbDeviceProtocol; + private GBDeviceProtocol gbDeviceProtocol; protected GBDeviceIoThread gbDeviceIOThread; /** @@ -59,7 +60,7 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport /** * Lazily creates and returns the GBDeviceProtocol instance to be used. */ - public synchronized GBDeviceProtocol getDeviceProtocol() { + protected synchronized GBDeviceProtocol getDeviceProtocol() { if (gbDeviceProtocol == null) { gbDeviceProtocol = createDeviceProtocol(); } @@ -82,13 +83,13 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport * * @param bytes the message to send to the device */ - protected void sendToDevice(byte[] bytes) { + private void sendToDevice(byte[] bytes) { if (bytes != null && gbDeviceIOThread != null) { gbDeviceIOThread.write(bytes); } } - public void handleGBDeviceEvent(GBDeviceEventSendBytes sendBytes) { + private void handleGBDeviceEvent(GBDeviceEventSendBytes sendBytes) { sendToDevice(sendBytes.encodedBytes); } @@ -226,4 +227,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport byte[] bytes = gbDeviceProtocol.encodeTestNewFunction(); sendToDevice(bytes); } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + byte[] bytes = gbDeviceProtocol.encodeSendWeather(weatherSpec); + 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 c4fe1e737..77a5bc2e3 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 @@ -7,6 +7,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; public abstract class GBDeviceProtocol { @@ -108,4 +109,7 @@ public abstract class GBDeviceProtocol { return mDevice; } + public byte[] encodeSendWeather(WeatherSpec weatherSpec) { + 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 3898818e2..66de19b56 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -46,14 +47,20 @@ public class FileUtils { } } + /** + * Copies the contents of the given input stream to the destination file. + * @param inputStream the contents to write. Note: the caller has to close the input stream! + * @param destFile the file to write to + * @throws IOException + */ public static void copyStreamToFile(InputStream inputStream, File destFile) throws IOException { - FileOutputStream fout = new FileOutputStream(destFile); - byte[] buf = new byte[4096]; - while (inputStream.available() > 0) { - int bytes = inputStream.read(buf); - fout.write(buf, 0, bytes); + try (FileOutputStream fout = new FileOutputStream(destFile)) { + byte[] buf = new byte[4096]; + while (inputStream.available() > 0) { + int bytes = inputStream.read(buf); + fout.write(buf, 0, bytes); + } } - fout.close(); } public static void copyURItoFile(Context ctx, Uri uri, File destFile) throws IOException { @@ -62,29 +69,47 @@ public class FileUtils { } ContentResolver cr = ctx.getContentResolver(); - InputStream fin; - try { - fin = new BufferedInputStream(cr.openInputStream(uri)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return; + InputStream in = cr.openInputStream(uri); + if (in == null) { + throw new IOException("unable to open input stream: " + uri); + } + try (InputStream fin = new BufferedInputStream(in)) { + copyStreamToFile(fin, destFile); + fin.close(); } - copyStreamToFile(fin, destFile); - fin.close(); } + /** + * Returns the textual contents of the given file. The contents is expected to be + * in UTF-8 encoding. + * @param file the file to read + * @return the file contents as a newline-delimited string + * @throws IOException + * @see #getStringFromFile(File, String) + */ public static String getStringFromFile(File file) throws IOException { + return getStringFromFile(file, StandardCharsets.UTF_8.name()); + } + + /** + * Returns the textual contents of the given file. The contents will be interpreted using the + * given encoding. + * @param file the file to read + * @return the file contents as a newline-delimited string + * @throws IOException + * @see #getStringFromFile(File) + */ + public static String getStringFromFile(File file, String encoding) throws IOException { FileInputStream fin = new FileInputStream(file); - BufferedReader reader = new BufferedReader(new InputStreamReader(fin)); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(fin, encoding))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + return sb.toString(); } - reader.close(); - fin.close(); - return sb.toString(); } /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java new file mode 100644 index 000000000..9c5d9b674 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/UriHelper.java @@ -0,0 +1,150 @@ +package nodomain.freeyourgadget.gadgetbridge.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +public class UriHelper { + @NonNull + private final Uri uri; + @NonNull + private final Context context; + private String fileName; + private long fileSize; + @Nullable + private File file; + + private UriHelper(@NonNull Uri uri, @NonNull Context context) { + this.uri = uri; + this.context = context; + } + + /** + * Returns the uri as passed to #get(Uri, Context) + */ + @NonNull + public Uri getUri() { + return uri; + } + + /** + * Returns the context as passed to #get(Uri, Context) + */ + @NonNull + public Context getContext() { + return context; + } + + /** + * Returns an immutable helper to access the given Uri. In case the uri cannot be read/resolved + * an IOException is thrown. + * @param uri the uri to access + * @param context the context for accessing uris + * @throws IOException + */ + @NonNull + public static UriHelper get(@NonNull Uri uri, @NonNull Context context) throws FileNotFoundException, IOException { + UriHelper helper = new UriHelper(uri, context); + helper.resolveMetadata(); + return helper; + } + + /** + * Opens a stream to read the contents of the uri. + * Note: the caller has to close the stream after usage. + * Every invocation of this method will open a new stream. + * @throws FileNotFoundException + */ + @NonNull + public InputStream openInputStream() throws FileNotFoundException { + ContentResolver cr = context.getContentResolver(); + InputStream inputStream = cr.openInputStream(uri); + if (inputStream != null) { + return new BufferedInputStream(inputStream); + } + throw new FileNotFoundException("Unable to open inputstream for " + uri); + } + + /** + * Returns the content length (file size) in bytes + */ + public long getFileSize() { + return fileSize; + } + + /** + * Returns the name of the file referenced by the Uri. Does not include the path. + */ + @NonNull + public String getFileName() { + return fileName; + } + + /** + * Returns the file behind the uri, or null in case it is not a file:/ Uri. + * @return the file or null + */ + @Nullable + public File getFile() { + return file; + } + + private void resolveMetadata() throws IOException { + String uriScheme = uri.getScheme(); + if (ContentResolver.SCHEME_CONTENT.equals(uriScheme)) { + Cursor cursor = context.getContentResolver().query( + uri, + new String[] { + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.SIZE + }, null, null, null); + if (cursor == null) { + throw new IOException("Unable to query metadata for: " + uri); + } + if (cursor.moveToFirst()) { + int name_index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + if (name_index == -1) { + throw new IOException("Unable to retrieve name for: " + uri); + } + int size_index = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE); + if (size_index == -1) { + throw new IOException("Unable to retrieve size for: " + uri); + } + try { + fileName = cursor.getString(name_index); + if (fileName == null) { + throw new IOException("Unable to retrieve name for: " + uri); + } + fileSize = cursor.getLong(size_index); + if (fileSize < 0) { + throw new IOException("Unable to retrieve size for: " + uri); + } + } catch (Exception ex) { + throw new IOException("Unable to retrieve metadata for: " + uri + ": " + ex.getMessage()); + } + } + } else if (ContentResolver.SCHEME_FILE.equals(uriScheme)) { + file = new File(uri.getPath()); + if (!file.exists()) { + throw new FileNotFoundException("Does not exist: " + file); + } + fileName = file.getName(); + fileSize = file.length(); + } else if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uriScheme)) { + // we could actually read it, but I don't see how we can determine the file size + throw new IOException("Unsupported scheme for uri: " + uri); + } else { + throw new IOException("Unsupported scheme for uri: " + uri); + } + } +} diff --git a/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java new file mode 100644 index 000000000..0fc34ab2c --- /dev/null +++ b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java @@ -0,0 +1,201 @@ +package ru.gelin.android.weather.notification; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ParcelableWeather2 implements Parcelable { + private static final Logger LOG = LoggerFactory.getLogger(ParcelableWeather2.class); + + // getters and setters suck ;) + + public long time = 0; + public long queryTime = 0; + public int version = 0; + public String location = ""; + public int currentTemp = 0; + public String currentCondition = ""; + + String[] currentConditionType = null; + public int currentConditionCode = 3200; + String[] forecastConditionType = null; + public int forecastConditionCode = 3200; + public int todayLowTemp = 0; + public int todayHighTemp = 0; + public int forecastLowTemp = 0; + public int forecastHighTemp = 0; + + + private ParcelableWeather2(Parcel in) { + int version = in.readInt(); + if (version != 2) { + return; + } + Bundle bundle = in.readBundle(); + + location = bundle.getString("weather_location"); + time = bundle.getLong("weather_time"); + queryTime = bundle.getLong("weather_query_time"); + bundle.getString("weather_forecast_url"); + int conditions = bundle.getInt("weather_conditions"); + if (conditions > 0) { + Bundle conditionBundle = in.readBundle(); + currentCondition = conditionBundle.getString("weather_condition_text"); + conditionBundle.getStringArray("weather_condition_types"); + currentTemp = conditionBundle.getInt("weather_current_temp"); + + currentConditionType = conditionBundle.getStringArray("weather_condition_types"); + currentConditionCode = weatherConditionTypesToOpenWeatherMapIds(currentConditionType[0]); + todayLowTemp = conditionBundle.getInt("weather_low_temp"); + todayHighTemp = conditionBundle.getInt("weather_high_temp"); + //fetch immediate next forecast + if (--conditions > 0) { + Bundle forecastBundle = in.readBundle(); + forecastConditionType = forecastBundle.getStringArray("weather_condition_types"); + forecastConditionCode = weatherConditionTypesToOpenWeatherMapIds(forecastConditionType[0]); + forecastLowTemp = forecastBundle.getInt("weather_low_temp"); + forecastHighTemp = forecastBundle.getInt("weather_high_temp"); + } + } + // get the rest + while (--conditions > 0) { + Bundle conditionBundle = in.readBundle(); + conditionBundle.getString("weather_condition_text"); + conditionBundle.getStringArray("weather_condition_types"); + conditionBundle.getInt("weather_current_temp"); + } + } + + public static final Creator CREATOR = new Creator() { + @Override + public ParcelableWeather2 createFromParcel(Parcel in) { + return new ParcelableWeather2(in); + } + + @Override + public ParcelableWeather2[] newArray(int size) { + return new ParcelableWeather2[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + // we do not really want to use this at all + } + + private int weatherConditionTypesToOpenWeatherMapIds(String weather_condition_type) { + switch (weather_condition_type) { + case "THUNDERSTORM_RAIN_LIGHT": + return 200; + case "THUNDERSTORM_RAIN": + return 201; + case "THUNDERSTORM_RAIN_HEAVY": + return 202; + case "THUNDERSTORM_LIGHT": + return 210; + case "THUNDERSTORM": + return 211; + case "THUNDERSTORM_HEAVY": + return 212; + case "THUNDERSTORM_RAGGED": + return 221; + case "THUNDERSTORM_DRIZZLE_LIGHT": + return 230; + case "THUNDERSTORM_DRIZZLE": + return 231; + case "THUNDERSTORM_DRIZZLE_HEAVY": + return 232; + + case "DRIZZLE_LIGHT": + return 300; + case "DRIZZLE": + return 301; + case "DRIZZLE_HEAVY": + return 302; + case "DRIZZLE_RAIN_LIGHT": + return 310; + case "DRIZZLE_RAIN": + return 311; + case "DRIZZLE_RAIN_HEAVY": + return 312; + case "DRIZZLE_SHOWER": + return 321; + + case "RAIN_LIGHT": + return 500; + case "RAIN": + return 501; + case "RAIN_HEAVY": + return 502; + case "RAIN_VERY_HEAVY": + return 503; + case "RAIN_EXTREME": + return 504; + case "RAIN_FREEZING": + return 511; + case "RAIN_SHOWER_LIGHT": + return 520; + case "RAIN_SHOWER": + return 521; + case "RAIN_SHOWER_HEAVY": + return 522; + + case "SNOW_LIGHT": + return 600; + case "SNOW": + return 601; + case "SNOW_HEAVY": + return 602; + case "SLEET": + return 611; + case "SNOW_SHOWER": + return 621; + + case "MIST": + return 701; + case "SMOKE": + return 711; + case "HAZE": + return 721; + case "SAND_WHIRLS": + return 731; + case "FOG": + return 741; + + case "CLOUDS_CLEAR": + return 800; + case "CLOUDS_FEW": + return 801; + case "CLOUDS_SCATTERED": + return 802; + case "CLOUDS_BROKEN": + return 803; + case "CLOUDS_OVERCAST": + return 804; + + case "TORNADO": + return 900; + case "TROPICAL_STORM": + return 901; + case "HURRICANE": + return 902; + case "COLD": + return 903; + case "HOT": + return 904; + case "WINDY": + return 905; + case "HAIL": + return 906; + } + return 3200; + } +} diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml index 436454388..766f6c5a2 100644 --- a/app/src/main/res/menu/appmanager_context.xml +++ b/app/src/main/res/menu/appmanager_context.xml @@ -21,6 +21,12 @@ + + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index f634a3405..46d61389c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,12 +6,13 @@ Déboguer Quitter Synchroniser - Moniteur de sommeil (ALPHA) - Trouver l\'appareil + Suivi du sommeil (ALPHA) + Retrouver un appareil perdu... Prendre une capture d\'écran Déconnexion - Supprimer l\'appareil - Supprimer %1$s + Supprimer l’appareil + Supprimer %1$s + Ceci va supprimer l’appareil et toutes les données associées ! Déboguer Gestionnaire d\'application @@ -21,8 +22,11 @@ Supprimer Supprimer et effacer du cache Réinstaller + Rechercher dans le magasin d’application Pebble Activer Désactiver + Activer la mesure du rythme cardiaque + Désactiver la mesure du rythme cardiaque Configurer Haut de page @@ -32,7 +36,7 @@ Vous êtes sur le point d\'installer le micrologiciel %s à la place de celui qui est actuellement sur votre Mi Band. Vous êtes sur le point d\'installer les micrologiciels %1$s et %2$s à la place de ceux qui sont actuellement sur votre Mi Band. Ce micrologiciel a été testé et est connu pour être compatible avec Gadgetbridge. - Ce micrologiciel n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge.\n\nIl n\'est pas conseillé de flasher votre Mi Band. + Ce micrologiciel n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge.\n\nIl n\'est pas conseillé de le flasher sur votre Mi Band. Si vous désirez continuer et que tout fonctionne correctement par la suite, SVP informez-en les développeurs de Gadgetbridge pour demander l\'ajout de ce micrologiciel à leur liste: %s Paramètres @@ -51,47 +55,53 @@ Notifications Répétitions Appels téléphoniques - SMS + Textos K9-Mail Messages Pebble Support des applications qui envoient des notification à Pebble par PebbleKit. Support des notifications génériques ... y compris quand l\'écran est allumé Ne Pas Déranger - Arrêter l’envoie des Notification non-désirée en mode Ne Pas Déranger. + Arrêter l’envoi des notifications non-désirées en mode Ne Pas Déranger. toujours quand l\'écran est éteint jamais Applications bloquées Modèles de messages Réponses - Suffixe commun + Suffixe fréquent Raccrocher Mise à jour Pebble Options développeur Adresse Mi Band Paramètres Pebble - Traqueur d\'activité + Traqueurs d\'activité Traqueur d\'activité préféré Synchroniser Pebble Health Synchroniser Misfit Synchroniser Morpheuz - Permettre l\'accès d\'applications tierces Android + Permettre l\'accès aux applications tierces Android Activer le support expérimental pour les applications Android utilisant PebbleKit Lever et coucher de soleil - Envoyer heures de lever et coucher du soleil dans la timeline Pebble en fonction de l\'emplacement + Envoyer les heures de lever et coucher du soleil dans l’historique Pebble en fonction de l\'emplacement Emplacement Obtenir l\'emplacement Latitude Longitude - Garder l\'emplacement à jour - Tenter d\'obtenir la localisation pendant la course à pied, utiliser la localisation enregistré en cas de problème. - Activez la localisation réseau s\'il vous plaît + Garder l’emplacement à jour + Essayer de garder la localisation à jour pendant le fonctionnement, sinon utiliser l’emplacement enregistré. + Veuillez activer la localisation réseau Emplacement obtenu - Protocole des notifications en vigueur + Forcer le protocole de notification Cette option force l\'utilisation du plus récent protocole de notification qui dépend de la version du micrologiciel. ACTIVEZ-LA UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES! Activer les fonctionnalités non-testées Activer les fonctionnalités non-testées. ACTIVEZ UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES! + Toujours préférer le BLE + Utiliser le support expérimental du LE pour toutes les Pebble au lieu du Bluetooth classique ; cela requiert l’appairage d\'une \"Pebble LE\" après qu’une non-LE ai déjà été connectée + Limite du GATT MTU de Pebble 2/LE + Si votre Pebble 2/LE ne fonctionne pas correctement, essayez d\'activer cette option pour limiter le MTU (plage valide 20-512) + Activer les logs des Watch App + Ceci permettra à Gadgetbridge de conserver les logs des Watch App (requiert une reconnexion) Tentatives de reconnexion non connecté connexion en cours @@ -106,29 +116,33 @@ Le Bluetooth n\'est pas supporté. Le Bluetooth est désactivé. Cliquez sur l\'appareil pour ouvrir le gestionnaire d\'application - Cliquez sur l\'appareil pour ouvrir l\'activité + Cliquez sur l\'appareil pour ouvrir le gestionnaire d’activité Cliquez sur connecter pour envoyer une vibration Tapotter sur le périphérique pour le connecter. - Connexion impossible. L’adresse Bluetooth est-elle invalide? + Connexion impossible. L’adresse Bluetooth est-elle valide? Gadgetbridge est en fonctionnement - Installation du binaire %1$d/%2$d - échec d\'installation! - Installation réalisé + Installation du fichier %1$d/%2$d + échec de l\'installation ! + Installation réalisée avec succès VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROCÉDEZ À VOS PROPRES RISQUES.\n\n\nCe micrologiciel est pour la version de matériel: %s Vous êtes sur le point d\'installer l\'application suivante:\n\n\n%1$s Version %2$s par %3$s\n N.D. Initialisé %1$s par %2$s - Découvrir les appareils - Arrêter le balayage - Démarrer le balayage + Scanner les appareils + Arrêter le scan + Démarrer le scan Connecter un nouvel appareil %1$s (%2$s) - Coupler l\'appareil - Utiliser le couplement Bluetooth d\'Android pour coupler l\'appareil - Coupler votre Mi Band - Coupler avec %s... - Aucune adresse mac fournie, ne peut être couplé + Appairer l\'appareil + Utiliser l’appairage Bluetooth d\'Android pour appairer l\'appareil + Appairer votre Mi Band + Appairage avec %s... + Création d’un lien avec %1$s (%2$s) + Impossible se s’appairer avec %1$s (%2$s) + Création du lien en cours : %1$s (%2$s) + Déjà lié avec %1$s (%2$s), connexion... + Aucune adresse MAC fournie, ne peut être appairé Paramètres spécifiques à l\'appareil Paramètres Mi Band Homme @@ -139,7 +153,7 @@ Aucune donnée utilisateur valide fournie, utilisation de données fictives pour le moment Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affilée. Installer - Rendre votre montre découvrable. Pour l\'instant les appareils connectés ne seront probablement pas découvert. Si votre montre n’apparaît pas après 2 minutes, essayer à nouveau après avoir redémarré. + Mettez votre appareil en mode visible. Les appareils déjà connectés ne sont pas visibles. Sur Android 6 ou supérieur, vous devez activer la localisation (ex. GPS). Si votre appareil n\'est pas visible après 2 minutes, réessayez après avoir redémarré votre téléphone. Note: Image de l\'appareil Nom/Pseudo @@ -149,7 +163,7 @@ Initialisation Récupération des données d\'activité De %1$s à %2$s - Côté de port du bracelet + Port main gauche ou droite? Profil de vibration Saccadé Court @@ -159,15 +173,15 @@ Sonnette Réveil Vibration - Notification SMS - Clavardage (Chat) - Navigation - Réseaux sociaux + Essayer + Notification Texto Paramètres des vibrations Notification générique - Notification Pebble - Notification e-mail + Notification des Mails Notification d\'appels entrants + Tchat + Navigation + Réseau social Trouver l\'appareil perdu Annuler pour arrêter les vibrations Votre activité @@ -193,7 +207,7 @@ Impossible de trouver un gestionnaire pour installer ce fichier. Impossible d\'installer le ficher suivant: %1$s Impossible d\'installer le micrologiciel spécifié: il ne correspond pas à la version du matériel de votre Pebble. - S\'il vous plait patientez pendant la détermination de l\'état de l\'installation... + Veuillez patienter pendant la détermination de l\'état de l\'installation... Niveau de batterie faible! %1$s batterie restante: %2$s%% Dernière charge: %s \n @@ -218,7 +232,7 @@ Échec lors de l\'écriture du micrologiciel Pas Activité en direct - Nombre de pas aujourd\'hui, objectif : %1$s + Nombre de pas aujourd\'hui, objectif: %1$s Ne pas confirmer le transfert de données d\'activités Les données d\'activités ne seront pas effacées du bracelet si elles ne sont pas confirmées. Utile si GB est utilisé avec d\'autres applications. Les données d\'activités seront conservées sur le Mi Band après la synchronisation. Utile si GB est utilisé avec d\'autres applications. @@ -241,11 +255,11 @@ Alarmes à réserver pour événements futurs Utiliser le capteur cardiaque pour améliorer la précision du sommeil La compensation de temps en heure (pour détecter le sommeil de travailleurs en rotation, par exemple) - Mi band 2 : format de l\'heure - Heure seule - - Allumer quand le poignet se lève - About to transfer data since %1$s + Mi2 : Format de la date + Heure + + Allumer l\'écran lors d\'un mouvement + Sur le point de transférer des données depuis %1$s en attente de reconnexion A propos de vous Année de naissance @@ -256,7 +270,7 @@ authentification requise ZzZz Ajouter un widget - Durée préférée de sommeil en heures + Préférer le mode heure pendant le sommeil Une alarme a été enregistré pour %1$02d:%2$02d Modèle: %1$s Micrologiciel: %1$s @@ -304,11 +318,14 @@ Si vous avez déjà importé vos données et êtes satisfait du résultat, vous Échec de la destruction de la base de donnée. Voulez vous détruire les anciennes activités de la base ? Voulez vous vraiment détruire entièrement la base de donnée ? Toutes vos données non importé seront perdu. - Les ancienne données d\'activité ont été effacés correctement. + Les anciennes données d\'activité ont été effacées correctement. Échec de la destruction de l\'ancienne base de donnée. Écraser Annuler Supprimer Vibration + + Appairage avec une Pebble + Une fenêtre d’appairage va s’afficher sur votre téléphone. Si cela ne se produit pas, regardez dans vos notifications et acceptez la demande d’appairage. Acceptez ensuite la demande d’appairage sur votre Pebble. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fe603a9c5..288bd93d8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -138,7 +138,6 @@ 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 @@ -299,4 +298,5 @@ Si possono importare i dati da Mi Band, Pebble Health e Morpheuz, NON quelli di Cancella Vibrazione + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4c993ae85..2f42f669e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -108,7 +108,6 @@ 올바르지 않은 사용자 정보입니다. 일단 임시 사용자 정보를 사용합니다. Mi Band가 진동하고 깜빡일 때, 연달아 몇 번 두드리세요. 설치 - 기기를 발견 가능하도록 설정하세요. 현재 연결된 기기들은 발견될 수 없습니다. 2분이 지나도 기기가 나타나지 않는다면 재부팅 후 다시 시도해보세요. 알림: 기기 이미지 이름/별명 @@ -131,8 +130,6 @@ SMS 알림 진동 설정 일반 알림 - Pebble 알림 - Mail 알림 걸려오는 전화 알림 잃어버린 기기 찾기 진동을 멈추려면 취소를 선택하세요. @@ -226,4 +223,6 @@ 심박수 삭제 + + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 885356aa3..2a4484ce2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -3,17 +3,32 @@ Gadgetbridge Gadgetbridge Ustawienia - Usuń błąd + Debuguj Zakończ Synchronizuj Monitor snu (ALPHA) Odnajdź zagubione urządzenie - Zrób printscreen + Zrób screena Rozłącz + Usuń urządzenie + Usuń %1$s + To usunie urządzenie oraz zgromadzone dane Usuń błąd Zarządzanie aplikacjami + Aplikacje w pamięci + Zainstalowane aplikacje + Zainstalowane tarcze Usuń + Odinstaluj i usuń z pamięci + Zainstaluj ponownie + Szukaj w Pebble Appstore + Aktywuj + Deaktywuj + Aktywuj HRM + Deaktywuj HRM + Konfiguruj + Przejdź do góry Czarna lista powiadomień @@ -26,11 +41,16 @@ Ustawienia Ustawienia ogólne Połącz z urządzeniem gdy Bluetooth jest włączone + Łącz automatycznie Domyślny odtwarzacz muzyki Domyślny Data i godzina Synchronizuj czas Synchronizuj czas urządzenia podczas połączenia gdy czas lub strefa czasowa zmienia się na Androidzie + Motyw + Jasny + Ciemny + Język Powiadomienia Powtórzenia Połączenia @@ -39,19 +59,41 @@ Wiadomości Pebble Obsługa ogólnych powiadomień ... także gdy ekran jest włączony + Nie przeszkadzaj zawsze gdy ekran jest wyłączony nigdy Czarna lista aplikacji + Wiadomości zwrotne + Odpowiedzi + Uaktualnij na Pebble Ustawienia programisty Adres Mi Band Ustawienia Pebble + Monitory aktywności + Preferowany monitor + Synchronizuj Pebble Health + Synchronizuj Misfit + Synchronizuj Morpheuz Zezwól zewnętrznym aplikacjom Android na dostęp Włącz eksperymentalną obsługę aplikacji android przez PebbleKit + Wschód i zachód + Wyślij wschód i zachód do linii czasu Pebble bazując na lokalizacji + Lokalizacja + Otrzymaj lokalizację + Szerokość + Długość + Utrzymuj aktualną lokalizację + Włącz usługę lokalizacji + Lokalizacja otrzymana Wymuś protokół komunikacji Ta opcja wymusza użycie najnowszego protokołu powiadomień w zależności od wersji firmware. WŁĄCZ JEDYNIE JEŚLI WIESZ CO ROBISZ! Włącz nietestowane funkcje Włącz nie testowane funkcje. WŁĄCZ JEDYNIE JEŚLI WIESZ CO ROBISZ! + Preferuj BLE + Pebble 2/LE GATT limit MTU + Jeśli Twój Pebble 2/ Pebble LE nie działa jak należy spróbuj ustawić ten limit MTU (zakres 20-512) + Włącz logowanie aplikacji Próby ponownego połączenia nie połączony łącze @@ -65,6 +107,8 @@ To jest testowe powiadomienie z Gadgetbridge Bluetooth nie jest obsługiwane Bluetooth jest wyłączone + Tapnij urządzenie aby uruchomić menadżer aplikacji + Dotknij urządzenie aby połączyć Nie można połączyć. Adres BT nieprawidłowy? Gadgetbridge działa Instalowanie binarki %1$d/%2$d @@ -116,9 +160,10 @@ Powiadomienie SMS Ustawienia wibracji Ogólne powiadomienia - Powiadomienia Pebble Powiadomienia email Powiadomienia o połaczeniach przychodzących + Nawigacja + Sieć spolecznościowa Odnajdź zagubione urządzenie Anuluj by przerwać wibracje. Twoja aktywność @@ -173,6 +218,7 @@ Nie wysyłaj danych aktywności Gdy dane aktywności nie są przesłane na opaskę, wtedy nie będą usuwane. Przydatne gdy Gadgetbridge jest używany wraz z innymi aplikacjami Dane aktywności będą zachowane na Mi Band nawet po synchronizacji. Przydatne gdy Gadgetbridge jest używany z innymi aplikacjami. + To może pomóc na urządzeniach gdzie uaktualnienie kończy się błędem Historia kroków Aktualnie kroków/min Kroków łącznie @@ -188,12 +234,38 @@ Niekompatybilny firmware Ten firmware nie jest kompatybilny z urządzeniem Alarmy zarezerwowane dla nadchodzących zdarzeń + Użyj czujnika tętna alby poprawić detekcje snu + Mi2: Format daty + Czas oczekiwanie na ponowne połaczenie O tobie Data urodzenia Płeć Wzrost w cm Waga w kg + Dodaj widget + Preferowana długość snu w godzinach + Hardware: %1$s + Firmware: %1$s + Trwa aktualizacja firmware + Puls + Puls + Import bazydanych + Import starszych danych aktywności + Zarządzanie bazą danych + Zarządzanie bazą danych + Importuj / Usuń starą bazę danych + Wyeksportowano do: %1$s + Błąd eksportu bazy: %1$s + Zaimportować? + Naprawdę chcesz napisać bazę? Wszystkie Twoje dane zostaną zastąpione. + Błąd importu bazy: %1$s + Nie znaleziono aktywności, nic do importu. + Nadpisz + Anuluj Usuń + + Wibracje + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2041edd10..7284db28f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -99,7 +99,6 @@ Сопряжение устройств Для сопряжения устройств используйте диалог Android. Сопряжение вашего Mi Band - Сопряжение с %s… MAC-адрес не был передан, сопряжение не удалось. Настройки устройства Настройки Mi Band @@ -133,8 +132,6 @@ SMS-уведомление Настройки вибро Общие уведомления - Уведомления Pebble - Уведомления почты Уведомления о входящем звонке Найти потерянное устройство Отмените, чтобы остановить вибро @@ -223,4 +220,6 @@ Обновление прошивки в процессе Удалить + + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 3779ce4aa..1d966d845 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -105,7 +105,6 @@ Створення пари з пристроєм Для створення пари із пристроєм використовуйте діалог Android. Створення пари із вашим Mi—Band - Створення пари із %s… MAC-адресу не було передано, не вдалося створити пару. Параметри пристрою Параметри Mi—Band @@ -138,8 +137,6 @@ SMS-сповіщення Параметри вібро Загальні сповіщення - Сповіщення Pebble - Сповіщення пошти Сповіщення під час вхідного дзвінку Знайти загублений пристрій Скасуйте, аби зупинити вібро @@ -220,4 +217,6 @@ ПЗ: %1$s Вилучити + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a9d2e3e2b..6430085cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,6 +30,8 @@ Deactivate Activate HRM Deactivate HRM + Activate system weather app + Deactivate system weather app Configure Move to top diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 7b1b8cb0a..d9e4591af 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,9 +1,15 @@ - New device: ZeBand - ZeBand: Initial experimental support - Pebble 2: Fix Pebble Classic FW 3.x app variant being priorized over native Pebble 2 app variant + New devices: HPlus (e.g. Zeblaze ZeBand), contributed by João Paulo Barraca + ZeBand: Initial support: notifications, heart rate, sleep monitoring, user configuration, date+time + Pebble 2: Fix Pebble Classic FW 3.x app variant being prioritized over native Pebble 2 app variant + Charts (Live Activity): Fix axis labels color in dark theme + Mi Band: Fix ginormous step count when using Live Activity + Mi Band: Improved performance during activity sync + Mi Band 2: Fix activity data missing after doing manual hr measurements or live activity + Support sharing firmwares/watchapps/watchfaces to Gadgetbridge + Support for the "Subsonic" music player (#474) Mi Band: Fix crash with unknown notification sources diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java index 68af48ad8..58dccd72b 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationServiceTestCase.java @@ -22,7 +22,7 @@ public class DeviceCommunicationServiceTestCase extends TestBase { * Factory that always returns the mockSupport instance */ private class TestDeviceSupportFactory extends DeviceSupportFactory { - public TestDeviceSupportFactory(Context context) { + TestDeviceSupportFactory(Context context) { super(context); } @@ -53,7 +53,7 @@ public class DeviceCommunicationServiceTestCase extends TestBase { mDeviceService = new TestDeviceService(getContext()); } - protected GBDevice getDevice() { + private GBDevice getDevice() { return realSupport.getDevice(); } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java index ba798a586..8eff6f79c 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceService.java @@ -13,11 +13,11 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; * Extends GBDeviceServer so that communication with the service works * with Robolectric. */ -public class TestDeviceService extends GBDeviceService { +class TestDeviceService extends GBDeviceService { private final ServiceController serviceController; private final DeviceCommunicationService service; - public TestDeviceService(Context context) throws Exception { + TestDeviceService(Context context) throws Exception { super(context); serviceController = Robolectric.buildService(DeviceCommunicationService.class, createIntent()); diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java index b4328421e..160b855b9 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java @@ -15,10 +15,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; -public class TestDeviceSupport extends AbstractDeviceSupport { +class TestDeviceSupport extends AbstractDeviceSupport { - public TestDeviceSupport() { + TestDeviceSupport() { } @Override @@ -177,4 +178,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport { public void onTestNewFunction() { } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } }