Merge branch 'master' of https://github.com/Freeyourgadget/Gadgetbridge into hplus-handle-data

here
João Paulo Barraca 2017-01-10 13:23:23 +00:00
commit 13ec497127
76 changed files with 2602 additions and 757 deletions

8
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,8 @@
####Your issue is:
*In case of a bug, do not forget to attach logs!*
####Your wearable device is:
*Please specify model and firmware version if possible*
####Your android version is:

View File

@ -1,5 +1,33 @@
###Changelog
####Version 0.17.0 (next)
* Add weather support through "Weather Notification" app
* Pebble: Support for build-in weather system app (FW 4.x)
* Pebble: Add weather support for various watchfaces
* Pebble: Add option to disable call display
* Pebble: Delete notifications that got dismissed on the phone
* Pebble 2/LE: Improve reliablitly and transfer speed
* Various fixes for K9 mail when using the generic notification receiver
####Version 0.16.0
* 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
####Version 0.15.1
* Improved handling of notifications for some apps
* Pebble 2/LE: Add setting to limit GATT MTU for debugging broken BLE stacks
* Mi Band 2: Display battery status
####Version 0.15.0
* New device: Liveview
* Liveview: initial support (set the time and receive notifications)

View File

@ -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 <cpfeiffer@users.noreply.github.com>
* Daniele Gobbetti <daniele+github@gobbetti.name>
* Julien Pivotto <roidelapluie@inuits.eu>
* João Paulo Barraca <jpbarraca@gmail.com>
* Steffen Liebergeld <perl@gmx.org>
* Lem Dulfo <lemuel.dulfo@gmail.com>
* Sergey Trofimov <sarg@sarg.org.ru>
* JohnnySun <bmy001@gmail.com>
* Uwe Hermann <uwe@hermann-uwe.de>
* Gergely Peidl <gergely@peidl.net>
* 0nse <0nse@users.noreply.github.com>
* Gergely Peidl <gergely@peidl.net>
* Christian Fischer <sw-dev@computerlyrik.de>
* Normano64 <per.bergqwist@gmail.com>
* 6arms1leg <m.brnsfld@googlemail.com>
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
* xzovy <caleb@caleb-cooper.net>
* xphnx <xphnx@users.noreply.github.com>
@ -56,7 +58,6 @@
* atkyritsis <at.kyritsis@gmail.com>
* andre <andre.buesgen@yahoo.de>
* Alexey Afanasev <avafanasiev@gmail.com>
* 6arms1leg <m.brnsfld@googlemail.com>
And all the Transifex translators, which I cannot automatically list, at the moment.

View File

@ -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)

View File

@ -26,8 +26,8 @@ android {
targetSdkVersion 23
// note: always bump BOTH versionCode and versionName!
versionName "0.15.0"
versionCode 77
versionName "0.17.0"
versionCode 81
}
buildTypes {
release {

View File

@ -191,6 +191,15 @@
<data android:mimeType="application/zip" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
<!-- to receive files from the "share" intent -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<service
@ -203,6 +212,21 @@
</service>
<service android:name=".service.DeviceCommunicationService" />
<receiver
android:name=".externalevents.WeatherNotificationReceiver"
android:enabled="true">
<intent-filter>
<action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_UPDATE_2" />
</intent-filter>
</receiver>
<activity android:name=".externalevents.WeatherNotificationConfig"
android:label="mockup">
<intent-filter>
<action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_SKIN_PREFERENCES"/>
</intent-filter>
</activity>
<receiver
android:name=".externalevents.BluetoothStateChangeReceiver"
android:exported="false">

View File

@ -171,6 +171,10 @@ public class GBApplication extends Application {
return prefs.getBoolean("log_to_file", false);
}
public static boolean minimizeNotification() {
return prefs.getBoolean("minimize_priority", false);
}
static void setupDatabase(Context context) {
DBOpenHelper helper = new DBOpenHelper(context, DATABASE_NAME, null);
SQLiteDatabase db = helper.getWritableDatabase();

View File

@ -152,6 +152,9 @@ public class FwAppInstallerActivity extends GBActivity implements InstallActivit
});
uri = getIntent().getData();
if (uri == null) { //for "share" intent
uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
}
installHandler = findInstallHandlerFor(uri);
if (installHandler == null) {
setInfoText(getString(R.string.installer_activity_unable_to_find_handler));

View File

@ -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:

View File

@ -16,7 +16,7 @@ public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment
//systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
//systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("b2cae818-10f8-46df-ad2b-98ad2254a3c1"), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_NOTIFICATIONS, "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("67a32d95-ef69-46d4-a0b9-854cc62f97f9"), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("18e443ce-38fd-47c8-84d5-6d0c775fbe55"), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
@ -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;

View File

@ -72,8 +72,6 @@ public class LiveActivityFragment extends AbstractChartFragment {
private TimestampTranslation tsTranslation;
private class Steps {
private int initialSteps;
private int steps;
private int lastTimestamp;
private int currentStepsPerMinute;
@ -90,39 +88,29 @@ public class LiveActivityFragment extends AbstractChartFragment {
}
public int getTotalSteps() {
return steps - initialSteps;
return steps;
}
public int getMaxStepsPerMinute() {
return maxStepsPerMinute;
}
public void updateCurrentSteps(int newSteps, int timestamp) {
public void updateCurrentSteps(int stepsDelta, int timestamp) {
try {
if (steps == 0) {
steps = newSteps;
steps += stepsDelta;
lastTimestamp = timestamp;
if (newSteps > 0) {
initialSteps = newSteps;
}
return;
}
if (newSteps >= steps) {
int stepsDelta = newSteps - steps;
int timeDelta = timestamp - lastTimestamp;
currentStepsPerMinute = calculateStepsPerMinute(stepsDelta, timeDelta);
if (currentStepsPerMinute > maxStepsPerMinute) {
maxStepsPerMinute = currentStepsPerMinute;
maxStepsResetCounter = 0;
}
steps = newSteps;
lastTimestamp = timestamp;
} else {
// TODO: handle new day?
int timeDelta = timestamp - lastTimestamp;
currentStepsPerMinute = calculateStepsPerMinute(stepsDelta, timeDelta);
if (currentStepsPerMinute > maxStepsPerMinute) {
maxStepsPerMinute = currentStepsPerMinute;
maxStepsResetCounter = 0;
}
steps += stepsDelta;
lastTimestamp = timestamp;
} catch (Exception ex) {
GB.toast(LiveActivityFragment.this.getContext(), ex.getMessage(), Toast.LENGTH_SHORT, GB.ERROR, ex);
}
@ -136,7 +124,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
throw new IllegalArgumentException("delta in seconds is <= 0 -- time change?");
}
int oneMinute = 60 * 1000;
int oneMinute = 60;
float factor = oneMinute / seconds;
int result = (int) (stepsDelta * factor);
if (result > MAX_STEPS_PER_MINUTE) {
@ -152,24 +140,27 @@ public class LiveActivityFragment extends AbstractChartFragment {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case DeviceService.ACTION_REALTIME_STEPS: {
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
int timestamp = translateTimestampFrom(intent);
addEntries(steps, timestamp);
break;
}
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: {
int heartRate = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, 0);
int timestamp = translateTimestampFrom(intent);
if (isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
case DeviceService.ACTION_REALTIME_SAMPLES: {
ActivitySample sample = (ActivitySample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
addSample(sample);
break;
}
}
}
};
private void addSample(ActivitySample sample) {
int heartRate = sample.getHeartRate();
int timestamp = tsTranslation.shorten(sample.getTimestamp());
if (isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
int steps = sample.getSteps();
if (steps != ActivitySample.NOT_MEASURED) {
addEntries(steps, timestamp);
}
}
private int translateTimestampFrom(Intent intent) {
return translateTimestamp(intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis()));
}
@ -251,8 +242,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS);
filterLocal.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
heartRateValues = new ArrayList<>();
tsTranslation = new TimestampTranslation();
@ -377,6 +367,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
// chart.getXAxis().setPosition(XAxis.XAxisPosition.TOP);
chart.getXAxis().setDrawLabels(false);
chart.getXAxis().setEnabled(false);
chart.getXAxis().setTextColor(CHART_TEXT_COLOR);
chart.getAxisLeft().setTextColor(CHART_TEXT_COLOR);
chart.setBackgroundColor(BACKGROUND_COLOR);
chart.getDescription().setTextColor(DESCRIPTION_COLOR);
chart.getDescription().setText(title);

View File

@ -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.
@ -21,6 +22,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
public interface EventHandler {
void onNotification(NotificationSpec notificationSpec);
void onDeleteNotification(int id);
void onSetTime();
void onSetAlarms(ArrayList<? extends Alarm> alarms);
@ -75,4 +78,6 @@ public interface EventHandler {
void onSendConfiguration(String config);
void onTestNewFunction();
void onSendWeather(WeatherSpec weatherSpec);
}

View File

@ -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) {

View File

@ -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;

View File

@ -1,6 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
@ -9,9 +8,7 @@ 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;
@ -26,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);
@ -45,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<PebbleInstallable> pebbleInstallables = null;
private boolean isFirmware = false;
@ -60,100 +57,45 @@ public class PBWReader {
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));
if (uri.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);
File f = new File(uri.getPath());
pebbleInstallables = new ArrayList<>();
pebbleInstallables.add(new PebbleInstallable("lang", (int) f.length(), 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 (!uri.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;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().startsWith(dir)) {
platformDir = dir;
break;
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
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")) {
@ -249,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;
@ -260,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;
}
@ -282,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
}

View File

@ -29,7 +29,13 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
MusicSpec musicSpec = new MusicSpec();
musicSpec.artist = intent.getStringExtra("artist");
musicSpec.album = intent.getStringExtra("album");
musicSpec.track = intent.getStringExtra("track");
if (intent.hasExtra("track")) {
musicSpec.track = intent.getStringExtra("track");
}
else if (intent.hasExtra("title")) {
musicSpec.track = intent.getStringExtra("title");
}
musicSpec.duration = intent.getIntExtra("duration", 0) / 1000;
if (!lastMusicSpec.equals(musicSpec)) {

View File

@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.AppNotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.LimitedQueue;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -251,6 +252,18 @@ public class NotificationListener extends NotificationListenerService {
notificationSpec.type = AppNotificationType.getInstance().get(source);
if (source.equals("com.fsck.k9")) {
// we dont want group summaries at all for k9
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
return;
}
preferBigText = true;
}
if (notificationSpec.type == null) {
notificationSpec.type = NotificationType.UNKNOWN;
}
LOG.info("Processing notification from source " + source + " with flags: " + notification.flags);
dissectNotificationTo(notification, notificationSpec, preferBigText);
@ -383,7 +396,23 @@ public class NotificationListener extends NotificationListenerService {
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
//FIXME: deduplicate code
String source = sbn.getPackageName();
Notification notification = sbn.getNotification();
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
return;
}
if (source.equals("android") ||
source.equals("com.android.systemui") ||
source.equals("com.android.dialer") ||
source.equals("com.cyanogenmod.eleven")) {
return;
}
LOG.info("notification removed, will ask device to delete it");
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
}
private void dumpExtras(Bundle bundle) {

View File

@ -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
}

View File

@ -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);
}
}
}

View File

@ -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<? extends Service> mServiceClass;
private final Class<? extends Service> mServiceClass;
public GBDeviceService(Context context) {
mContext = context;
@ -106,6 +105,14 @@ public class GBDeviceService implements DeviceService {
invokeService(intent);
}
@Override
public void onDeleteNotification(int id) {
Intent intent = createIntent().setAction(ACTION_DELETE_NOTIFICATION)
.putExtra(EXTRA_NOTIFICATION_ID, id);
invokeService(intent);
}
@Override
public void onSetTime() {
Intent intent = createIntent().setAction(ACTION_SETTIME);
@ -293,4 +300,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);
}
}

View File

@ -29,6 +29,9 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
// Conversations
put("eu.siacs.conversations", NotificationType.CONVERSATIONS);
// Riot
put("im.vector.alpha", NotificationType.RIOT);
// Signal
put("org.thoughtcrime.securesms", NotificationType.SIGNAL);

View File

@ -16,6 +16,7 @@ public interface DeviceService extends EventHandler {
String ACTION_START = PREFIX + ".action.start";
String ACTION_CONNECT = PREFIX + ".action.connect";
String ACTION_NOTIFICATION = PREFIX + ".action.notification";
String ACTION_DELETE_NOTIFICATION = PREFIX + ".action.delete_notification";
String ACTION_CALLSTATE = PREFIX + ".action.callstate";
String ACTION_SETCANNEDMESSAGES = PREFIX + ".action.setcannedmessages";
String ACTION_SETTIME = PREFIX + ".action.settime";
@ -38,17 +39,13 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
/**
* Use EXTRA_REALTIME_SAMPLE instead
*/
@Deprecated
String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement";
String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent";
String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent";
String 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";
@ -85,6 +82,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
*/

View File

@ -13,6 +13,7 @@ public enum NotificationType {
GENERIC_SMS(PebbleIconID.GENERIC_SMS, PebbleColor.VividViolet),
FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.Liberty),
FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.VeryLightBlue),
RIOT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo),
SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon),
TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.PictonBlue),
@ -46,6 +47,7 @@ public enum NotificationType {
return "generic_social";
case CONVERSATIONS:
case FACEBOOK_MESSENGER:
case RIOT:
case SIGNAL:
case TELEGRAM:
case WHATSAPP:

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
@ -59,6 +60,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CA
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_CALENDAREVENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_NOTIFICATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
@ -73,6 +75,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 +126,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);
@ -343,6 +356,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.onNotification(notificationSpec);
break;
}
case ACTION_DELETE_NOTIFICATION: {
mDeviceSupport.onDeleteNotification(intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1));
break;
}
case ACTION_ADD_CALENDAREVENT: {
CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
calendarEventSpec.id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
@ -501,6 +518,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;
@ -576,6 +608,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mMusicPlaybackReceiver = new MusicPlaybackReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.android.music.metachanged");
filter.addAction("net.sourceforge.subsonic.androidapp.EVENT_META_CHANGED");
//filter.addAction("com.android.music.playstatechanged");
registerReceiver(mMusicPlaybackReceiver, filter);
}

View File

@ -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.
@ -134,6 +135,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onNotification(notificationSpec);
}
@Override
public void onDeleteNotification(int id) {
delegate.onDeleteNotification(id);
}
@Override
public void onSetTime() {
if (checkBusy("set time") || checkThrottle("set time")) {
@ -335,4 +341,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onTestNewFunction();
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
if (checkBusy("send weather event")) {
return;
}
delegate.onSendWeather(weatherSpec);
}
}

View File

@ -1,7 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothGattCharacteristic;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -178,64 +181,12 @@ public class GattCharacteristic {
public static final UUID UUID_CHARACTERISTIC_WIND_CHILL = UUID.fromString((String.format(AbstractBTLEDeviceSupport.BASE_UUID, "2A79")));
//do we need this?
private static Map<UUID, String> GATTCHARACTERISTIC_DEBUG;
private static final Map<UUID, String> GATTCHARACTERISTIC_DEBUG;
static {
GATTCHARACTERISTIC_DEBUG = new HashMap<>();
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_ALERT_CATEGORY_ID, "Alert AlertCategory ID");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_ALERT_CATEGORY_ID_BIT_MASK, "Alert AlertCategory ID Bit Mask");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_ALERT_LEVEL, "Alert Level");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_ALERT_NOTIFICATION_CONTROL_POINT, "Alert Notification Control Point");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_ALERT_STATUS, "Alert Status");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GAP_APPEARANCE, "Appearance");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_BLOOD_PRESSURE_FEATURE, "Blood Pressure Feature");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_BLOOD_PRESSURE_MEASUREMENT, "Blood Pressure Measurement");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_BODY_SENSOR_LOCATION, "Body Sensor Location");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_CURRENT_TIME, "Current Time");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_DATE_TIME, "Date Time");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_DAY_DATE_TIME, "Day Date Time");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_DAY_OF_WEEK, "Day of Week");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GAP_DEVICE_NAME, "Device Name");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_DST_OFFSET, "DST Offset");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_EXACT_TIME_256, "Exact Time 256");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING, "Firmware Revision String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING, "Hardware Revision String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT, "Heart Rate Control Point");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT, "Heart Rate Measurement");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST, "IEEE 11073-20601 Regulatory");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_INTERMEDIATE_BLOOD_PRESSURE, "Intermediate Cuff Pressure");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_INTERMEDIATE_TEMPERATURE, "Intermediate Temperature");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_LOCAL_TIME_INFORMATION, "Local Time Information");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING, "Manufacturer Name String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_MEASUREMENT_INTERVAL, "Measurement Interval");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING, "Model Number String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_NEW_ALERT, "New Alert");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS, "Peripheral Preferred Connection Parameters");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GAP_PERIPHERAL_PRIVACY_FLAG, "Peripheral Privacy Flag");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GAP_RECONNECTION_ADDRESS, "Reconnection Address");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_REFERENCE_TIME_INFORMATION, "Reference Time Information");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_RINGER_CONTROL_POINT, "Ringer Control Point");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_RINGER_SETTING, "Ringer Setting");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING, "Serial Number String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_GATT_SERVICE_CHANGED, "Service Changed");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING, "Software Revision String");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_SUPPORTED_NEW_ALERT_CATEGORY, "Supported New Alert AlertCategory");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_SUPPORTED_UNREAD_ALERT_CATEGORY, "Supported Unread Alert AlertCategory");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_SYSTEM_ID, "System ID");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TEMPERATURE_MEASUREMENT, "Temperature Measurement");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TEMPERATURE_TYPE, "Temperature DeviceType");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_ACCURACY, "Time Accuracy");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_SOURCE, "Time Source");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_UPDATE_CONTROL_POINT, "Time Update Control Point");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_UPDATE_STATE, "Time Update State");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_WITH_DST, "Time with DST");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TIME_ZONE, "Time Zone");
GATTCHARACTERISTIC_DEBUG.put(UUID_CHARACTERISTIC_TX_POWER_LEVEL, "Tx Power Level");
}
public static String lookup(UUID uuid, String fallback) {
public static synchronized String lookup(UUID uuid, String fallback) {
if (GATTCHARACTERISTIC_DEBUG == null) {
GATTCHARACTERISTIC_DEBUG = initDebugMap();
}
String name = GATTCHARACTERISTIC_DEBUG.get(uuid);
if (name == null) {
name = fallback;
@ -243,6 +194,42 @@ public class GattCharacteristic {
return name;
}
private static Map<UUID, String> initDebugMap() {
Map<UUID,String> map = new HashMap<>();
try {
for (Field field : GattCharacteristic.class.getDeclaredFields()) {
if ((field.getModifiers() & Modifier.STATIC) != 0 && field.getType() == UUID.class) {
UUID uuid = (UUID) field.get(null);
if (uuid != null) {
map.put(uuid, toPrettyName(field.getName()));
}
}
}
} catch (Exception ex) {
Log.w(GattCharacteristic.class.getName(), "Error reading UUID fields by reflection: " + ex.getMessage(), ex);
}
return map;
}
private static String toPrettyName(String fieldName) {
String[] words = fieldName.split("_");
if (words.length <= 1) {
return fieldName.toLowerCase();
}
StringBuilder builder = new StringBuilder(fieldName.length());
for (String word : words) {
if (word.length() == 0 || "UUID".equals(word) || "CHARACTERISTIC".equals(word)) {
continue;
}
if (builder.length() > 0) {
builder.append(" ");
}
builder.append(word.toLowerCase());
}
return builder.toString();
}
public static String toString(BluetoothGattCharacteristic characteristic) {
return characteristic.getUuid() + " (" + lookup(characteristic.getUuid(), "unknown") + ")";
}

View File

@ -33,6 +33,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;
@ -429,6 +430,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
showText(notificationSpec.title, notificationSpec.body);
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
TransactionBuilder builder = new TransactionBuilder("time");
@ -631,6 +637,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
LOG.debug("Test New Function");
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
private void showIncomingCall(String name, String number) {
try {

View File

@ -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;
@ -544,6 +545,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
performPreferredNotification(origin + " received", origin, null);
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
try {
@ -950,30 +956,25 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
MiBandSampleProvider provider = new MiBandSampleProvider(gbDevice, session);
MiBandActivitySample sample = createActivitySample(device, user, ts, provider);
sample.setHeartRate(getHeartrateBpm());
sample.setSteps(getSteps());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(MiBandSampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
// TODO: remove this once fully ported to REALTIME_SAMPLES
if (sample.getSteps() != ActivitySample.NOT_MEASURED) {
Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps())
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) {
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate())
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
// Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
// .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
LOG.debug("Storing realtime sample: " + sample);
provider.addGBActivitySample(sample);
// set the steps only afterwards, since realtime steps are also recorded
// in the regular samples and we must not count them twice
// Note: we know that the DAO sample is never committed again, so we simply
// change the value here in memory.
sample.setSteps(getSteps());
if (LOG.isDebugEnabled()) {
LOG.debug("realtime sample: " + sample);
}
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
} catch (Exception e) {
LOG.warn("Unable to acquire db for saving realtime samples", e);
}
@ -1211,6 +1212,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) {

View File

@ -22,6 +22,7 @@ public abstract class RealtimeSamplesSupport {
protected int steps;
protected int heartrateBpm;
private int lastSteps;
// subclasses may add more
private Timer realtimeStorageTimer;
@ -56,12 +57,27 @@ public abstract class RealtimeSamplesSupport {
return realtimeStorageTimer != null;
}
public void setSteps(int stepsPerMinute) {
public synchronized void setSteps(int stepsPerMinute) {
this.steps = stepsPerMinute;
}
public int getSteps() {
return steps;
/**
* Returns the number of steps recorded since the last measurements. If no
* steps are available yet, ActivitySample.NOT_MEASURED is returned.
* @return
*/
public synchronized int getSteps() {
if (steps == ActivitySample.NOT_MEASURED) {
return ActivitySample.NOT_MEASURED;
}
if (lastSteps == 0) {
return ActivitySample.NOT_MEASURED; // wait until we have a delta between two samples
}
int delta = steps - lastSteps;
if (delta < 0) {
return 0;
}
return delta;
}
public void setHeartrateBpm(int hrBpm) {
@ -77,7 +93,10 @@ public abstract class RealtimeSamplesSupport {
resetCurrentValues();
}
protected void resetCurrentValues() {
protected synchronized void resetCurrentValues() {
if (steps >= lastSteps) {
lastSteps = steps;
}
steps = ActivitySample.NOT_MEASURED;
heartrateBpm = ActivitySample.NOT_MEASURED;
}

View File

@ -55,6 +55,7 @@ public class FetchActivityOperation extends AbstractMiBand1Operation {
private final boolean hasExtendedActivityData;
private static class ActivityStruct {
private int lastNotifiedProgress;
private final byte[] activityDataHolder;
private final int activityDataHolderSize;
//index of the buffer above
@ -129,6 +130,7 @@ public class FetchActivityOperation extends AbstractMiBand1Operation {
public void bufferFlushed(int minutes) {
activityDataTimestampProgress.add(Calendar.MINUTE, minutes);
activityDataHolderProgress = 0;
lastNotifiedProgress = 0;
}
}
@ -199,9 +201,16 @@ public class FetchActivityOperation extends AbstractMiBand1Operation {
} else {
bufferActivityData(value);
}
LOG.debug("activity data: length: " + value.length + ", remaining bytes: " + activityStruct.activityDataRemainingBytes);
if (LOG.isDebugEnabled()) {
LOG.debug("activity data: length: " + value.length + ", remaining bytes: " + activityStruct.activityDataRemainingBytes);
}
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), true, (int) (((float) (activityStruct.activityDataUntilNextHeader - activityStruct.activityDataRemainingBytes)) / activityStruct.activityDataUntilNextHeader * 100), getContext());
int progress = (int) (((float) (activityStruct.activityDataUntilNextHeader - activityStruct.activityDataRemainingBytes)) / activityStruct.activityDataUntilNextHeader * 100);
// avoid too many notifications overloading the system
if (progress - activityStruct.lastNotifiedProgress >= 8) {
activityStruct.lastNotifiedProgress = progress;
GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), true, progress, getContext());
}
if (activityStruct.isBlockFinished()) {
sendAckDataTransfer(activityStruct.activityDataTimestampToAck, activityStruct.activityDataUntilNextHeader);

View File

@ -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;
@ -70,7 +71,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.Dev
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.CheckAuthenticationNeededAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchActivityOperation;
@ -594,6 +594,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
performPreferredNotification(origin + " received", origin, alertLevel, null);
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
try {
@ -983,26 +988,22 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
sample.setRawKind(MiBand2SampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
// TODO: remove this once fully ported to REALTIME_SAMPLES
if (sample.getSteps() != ActivitySample.NOT_MEASURED) {
Intent intent = new Intent(DeviceService.ACTION_REALTIME_STEPS)
.putExtra(DeviceService.EXTRA_REALTIME_STEPS, sample.getSteps())
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
if (sample.getHeartRate() != ActivitySample.NOT_MEASURED) {
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, sample.getHeartRate())
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
// Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
// .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
// LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
LOG.debug("Storing realtime sample: " + sample);
provider.addGBActivitySample(sample);
// set the steps only afterwards, since realtime steps are also recorded
// in the regular samples and we must not count them twice
// Note: we know that the DAO sample is never committed again, so we simply
// change the value here in memory.
sample.setSteps(getSteps());
if (LOG.isDebugEnabled()) {
LOG.debug("realtime sample: " + sample);
}
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
} catch (Exception e) {
LOG.warn("Unable to acquire db for saving realtime samples", e);
}
@ -1251,6 +1252,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);

View File

@ -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<MiBandActivitySample> 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) {

View File

@ -7,11 +7,13 @@ import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
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;
@ -27,10 +29,17 @@ public class AppMessageHandler {
}
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
// Just ACK
GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
return new GBDeviceEvent[]{sendBytesAck};
}
public GBDeviceEvent[] onAppStart() {
return null;
}
public GBDeviceEvent[] pushMessage() {
public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
return null;
}

View File

@ -0,0 +1,54 @@
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<Pair<Integer, Object>> 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[] 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);
}
}

View File

@ -0,0 +1,55 @@
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<Pair<Integer, Object>> 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[] 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);
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -0,0 +1,70 @@
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 AppMessageHandlerSquare extends AppMessageHandler {
// "CfgKeyCelsiusTemperature":10001,
// CfgKeyConditions":10002,
//"CfgKeyWeatherError":10003,
// "CfgKeyWeatherMode":10004,
// "CfgKeyUseCelsius":10005,"
// CfgKeyWeatherLocation":10006,"
// "CfgKeyTemperature":10000,
//
//
private static final int KEY_TEMP = 10001; //celsius
private static final int KEY_WEATHER = 10002;
private static final int KEY_WEATHER_MODE = 10004;
private static final int KEY_USE_CELSIUS = 10005; //celsius
private static final int KEY_LOCATION = 10006;
private static final int KEY_TEMP_F = 10000; //fahrenheit
AppMessageHandlerSquare(UUID uuid, PebbleProtocol pebbleProtocol) {
super(uuid, pebbleProtocol);
}
private byte[] encodeSquareWeatherMessage(WeatherSpec weatherSpec) {
if (weatherSpec == null) {
return null;
}
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
pairs.add(new Pair<>(KEY_WEATHER_MODE, (Object) 1));
pairs.add(new Pair<>(KEY_WEATHER, (Object) weatherSpec.currentCondition));
pairs.add(new Pair<>(KEY_USE_CELSIUS, (Object) 1));
pairs.add(new Pair<>(KEY_TEMP, (Object) (weatherSpec.currentTemp - 273)));
pairs.add(new Pair<>(KEY_LOCATION, (Object) (weatherSpec.location)));
byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs);
ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
buf.put(weatherMessage);
return buf.array();
}
@Override
public GBDeviceEvent[] onAppStart() {
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec == null) {
return new GBDeviceEvent[]{null};
}
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeSquareWeatherMessage(weatherSpec);
return new GBDeviceEvent[]{sendBytes};
}
@Override
public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
return encodeSquareWeatherMessage(weatherSpec);
}
}

View File

@ -1,109 +1,127 @@
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<Pair<Integer, Object>> 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<Pair<Integer, Object>> 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<Pair<Integer, Object>> 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<Pair<Integer, Object>> pairs) {
return null;
/*
public GBDeviceEvent[] onAppStart() {
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec == null) {
return new GBDeviceEvent[]{null};
}
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeTimeStylePebbleConfig();
sendBytes.encodedBytes = encodeTimeStylePebbleWeather(weatherSpec);
return new GBDeviceEvent[]{sendBytes};
*/
}
}
@Override
public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
return encodeTimeStylePebbleWeather(weatherSpec);
}
}

View File

@ -0,0 +1,77 @@
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<Pair<Integer, Object>> 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[] 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);
}
}

View File

@ -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<Pair<Integer, Object>> 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<Pair<Integer, Object>> pairs) {
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeWeatherNeatMessage("Berlin", "22 C", "cloudy", 0);
return new GBDeviceEvent[]{sendBytes};
}
}

View File

@ -0,0 +1,82 @@
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 AppMessageHandlerZalewszczak extends AppMessageHandler {
private static final int KEY_ICON = 0;
private static final int KEY_TEMP = 1; //celsius
AppMessageHandlerZalewszczak(UUID uuid, PebbleProtocol pebbleProtocol) {
super(uuid, pebbleProtocol);
}
/*
* converted to JAVA from original JS
*/
private int getIconForConditionCode(int conditionCode) {
if (conditionCode < 300) {
return 7;
} else if (conditionCode < 400) {
return 6;
} else if (conditionCode == 511) {
return 8;
} else if (conditionCode < 600) {
return 6;
} else if (conditionCode < 700) {
return 8;
} else if (conditionCode < 800) {
return 10;
} else if (conditionCode == 800) {
return 1;
} else if (conditionCode == 801) {
return 2;
} else if (conditionCode < 900) {
return 5;
} else {
return 0;
}
}
private byte[] encodeWeatherMessage(WeatherSpec weatherSpec) {
if (weatherSpec == null) {
return null;
}
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
pairs.add(new Pair<>(KEY_TEMP, (Object) (Math.round(weatherSpec.currentTemp - 273) + "C")));
pairs.add(new Pair<>(KEY_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode))));
byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs);
ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
buf.put(weatherMessage);
return buf.array();
}
@Override
public GBDeviceEvent[] onAppStart() {
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec();
if (weatherSpec == null) {
return new GBDeviceEvent[]{null};
}
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
sendBytes.encodedBytes = encodeWeatherMessage(weatherSpec);
return new GBDeviceEvent[]{sendBytes};
}
@Override
public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) {
return encodeWeatherMessage(weatherSpec);
}
}

View File

@ -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);

View File

@ -613,6 +613,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;
@ -623,7 +627,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;
}

View File

@ -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,49 @@ 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
public static final UUID UUID_NOTIFICATIONS = UUID.fromString("b2cae818-10f8-46df-ad2b-98ad2254a3c1");
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_SQUARE = UUID.fromString("cb332373-4ee5-4c5c-8912-4f62af2d756c");
private static final UUID UUID_ZALEWSZCZAK_CROWEX = UUID.fromString("a88b3151-2426-43c6-b1d0-9b288b3ec47e");
private static final UUID UUID_ZALEWSZCZAK_FANCY = UUID.fromString("014e17bf-5878-4781-8be1-8ef998cee1ba");
private static final UUID UUID_ZALEWSZCZAK_TALLY = UUID.fromString("abb51965-52e2-440a-b93c-843eeacb697d");
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<UUID, AppMessageHandler> 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));
mAppMessageHandlers.put(UUID_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this));
}
private final HashMap<Byte, DatalogSession> 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);
@ -477,6 +483,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
}
@Override
public byte[] encodeDeleteNotification(int id) {
return encodeBlobdb(new UUID(GB_UUID_MASK, id), BLOBDB_DELETE, BLOBDB_NOTIFICATION, null);
}
@Override
public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong();
@ -502,6 +513,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 +544,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 +781,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);
}
@ -888,19 +922,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
}
UUID uuid = UUID.randomUUID();
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.putInt((int) (uuid.getLeastSignificantBits() >>> 32));
buf.putInt(id);
buf.putLong(uuid.getMostSignificantBits());
buf.putInt((int) (uuid.getLeastSignificantBits() >>> 32));
buf.putInt(id);
buf.putLong(GB_UUID_MASK);
buf.putLong(id);
buf.putLong(UUID_NOTIFICATIONS.getMostSignificantBits());
buf.putLong(UUID_NOTIFICATIONS.getLeastSignificantBits());
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(timestamp); // 32-bit timestamp
buf.putShort((short) 0); // duration
@ -1002,6 +1033,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 +1248,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 +1398,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 +1424,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 +1442,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 +1569,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 +1603,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 +1616,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 +1628,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 +1639,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 +1650,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 +1675,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 +1712,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);
@ -1528,7 +1745,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
switch (type) {
case TYPE_INT:
case TYPE_UINT:
dict.add(new Pair<Integer, Object>(key, buf.getInt()));
if (length == 1) {
dict.add(new Pair<Integer, Object>(key, buf.get()));
} else if (length == 2) {
dict.add(new Pair<Integer, Object>(key, buf.getShort()));
} else {
dict.add(new Pair<Integer, Object>(key, buf.getInt()));
}
break;
case TYPE_CSTRING:
case TYPE_BYTEARRAY:
@ -1910,10 +2133,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 +2468,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 +2480,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;

View File

@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
@ -18,6 +19,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;
@ -119,7 +121,11 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
@Override
public void onSetCallState(CallSpec callSpec) {
if (reconnect()) {
super.onSetCallState(callSpec);
if (callSpec.command == CallSpec.CALL_OUTGOING) {
if (GBApplication.getPrefs().getBoolean("pebble_enable_outgoing_call",true)) {
super.onSetCallState(callSpec);
}
}
}
}
@ -170,4 +176,11 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
super.onTestNewFunction();
}
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
if (reconnect()) {
super.onSendWeather(weatherSpec);
}
}
}

View File

@ -111,6 +111,11 @@ class PebbleGATTServer extends BluetoothGattServerCallback {
int serial = header >> 3;
if (command == 0x01) {
LOG.info("got ACK for serial = " + serial);
if (mPebbleLESupport.mPPAck != null) {
mPebbleLESupport.mPPAck.countDown();
} else {
LOG.warn("mPPAck countdownlatch is not present but it probably should");
}
}
if (command == 0x02) { // some request?
LOG.info("got command 0x02");

View File

@ -9,6 +9,9 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.CountDownLatch;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
public class PebbleLESupport {
private static final Logger LOG = LoggerFactory.getLogger(PebbleLESupport.class);
@ -19,7 +22,9 @@ public class PebbleLESupport {
private PipedInputStream mPipedInputStream;
private PipedOutputStream mPipedOutputStream;
private int mMTU = 20;
private int mMTULimit = Integer.MAX_VALUE;
boolean mIsConnected = false;
public CountDownLatch mPPAck;
public PebbleLESupport(Context context, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) throws IOException {
mBtDevice = btDevice;
@ -31,6 +36,9 @@ public class PebbleLESupport {
} catch (IOException e) {
LOG.warn("could not connect input stream");
}
mMTULimit = GBApplication.getPrefs().getInt("pebble_mtu_limit", 512);
mMTULimit = Math.max(mMTULimit, 20);
mMTULimit = Math.min(mMTULimit, 512);
mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice);
if (mPebbleGATTServer.initialize()) {
@ -99,7 +107,7 @@ public class PebbleLESupport {
}
void setMTU(int mtu) {
mMTU = mtu;
mMTU = Math.min(mtu, mMTULimit);
}
private class PipeReader extends Thread {
@ -129,6 +137,7 @@ public class PebbleLESupport {
int payloadToSend = bytesRead + 4;
int srcPos = 0;
mPPAck = new CountDownLatch(1);
while (payloadToSend > 0) {
int chunkSize = (payloadToSend < (mMTU - 4)) ? payloadToSend : mMTU - 4;
byte[] outBuf = new byte[chunkSize + 1];
@ -139,7 +148,9 @@ public class PebbleLESupport {
payloadToSend -= chunkSize;
}
Thread.sleep(500); // FIXME ugly wait 0.5s after each pebble package send to the pebble (we do not wait for the GATT chunks)
mPPAck.await();
mPPAck = null;
} catch (IOException | InterruptedException e) {
LOG.info(e.getMessage());
Thread.currentThread().interrupt();

View File

@ -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;
@ -123,6 +124,11 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
public void onNotification(NotificationSpec notificationSpec) {
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
@ -280,4 +286,9 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
}

View File

@ -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);
}
@ -107,6 +108,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
sendToDevice(bytes);
}
@Override
public void onDeleteNotification(int id) {
byte[] bytes = gbDeviceProtocol.encodeDeleteNotification(id);
sendToDevice(bytes);
}
@Override
public void onSetTime() {
byte[] bytes = gbDeviceProtocol.encodeSetTime();
@ -226,4 +233,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);
}
}

View File

@ -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 {
@ -20,6 +21,10 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeDeleteNotification(int id) {
return null;
}
public byte[] encodeSetTime() {
return null;
}
@ -108,4 +113,7 @@ public abstract class GBDeviceProtocol {
return mDevice;
}
public byte[] encodeSendWeather(WeatherSpec weatherSpec) {
return null;
}
}

View File

@ -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();
}
/**

View File

@ -65,6 +65,9 @@ public class GB {
if (GBApplication.isRunningLollipopOrLater()) {
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
}
if (GBApplication.minimizeNotification()) {
builder.setPriority(Notification.PRIORITY_MIN);
}
return builder.build();
}

View File

@ -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);
}
}
}

View File

@ -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<ParcelableWeather2> CREATOR = new Creator<ParcelableWeather2>() {
@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;
}
}

View File

@ -10,7 +10,5 @@
android:descendantFocusability="blocksDescendants"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/alarm_list"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
android:id="@+id/alarm_list" />
</FrameLayout>

View File

@ -21,6 +21,12 @@
<item
android:id="@+id/appmanager_hrm_deactivate"
android:title="@string/appmanager_hrm_deactivate"/>
<item
android:id="@+id/appmanager_weather_activate"
android:title="@string/appmanager_weather_activate" />
<item
android:id="@+id/appmanager_weather_deactivate"
android:title="@string/appmanager_weather_deactivate" />
<item
android:id="@+id/appmanager_app_configure"
android:title="@string/app_configure"/>

View File

@ -22,8 +22,11 @@
<string name="appmananger_app_delete">Löschen</string>
<string name="appmananger_app_delete_cache">Löschen und aus dem Zwischenspeicher entfernen</string>
<string name="appmananger_app_reinstall">Erneut installieren</string>
<string name="appmanager_app_openinstore">Im Pebble Appstore suchen</string>
<string name="appmanager_health_activate">aktivieren</string>
<string name="appmanager_health_deactivate">deaktivieren</string>
<string name="appmanager_hrm_activate">HRM aktivieren</string>
<string name="appmanager_hrm_deactivate">HRM deaktivieren</string>
<string name="app_configure">Konfigurieren</string>
<string name="app_move_to_top">Nach oben</string>
<!--Strings related to AppBlacklist-->
@ -85,12 +88,20 @@
<string name="pref_title_location_aquire">Standort Bestimmen</string>
<string name="pref_title_location_latitude">Breitengrad</string>
<string name="pref_title_location_longitude">Längengrad</string>
<string name="pref_title_location_keep_uptodate">Automatisch Standort aktualisieren</string>
<string name="pref_summary_location_keep_uptodate">Versuche den aktuellen Standort zur Laufzeit abzufragen und nutze die gespeicherten Standort falls das fehlschlägt</string>
<string name="toast_enable_networklocationprovider">Bitte ungefähre Standortbestimmung einschalten</string>
<string name="toast_aqurired_networklocation">Standort wurde bestimmt</string>
<string name="pref_title_pebble_forceprotocol">Benachrichtigungsprotokoll erzwingen</string>
<string name="pref_summary_pebble_forceprotocol">Diese Option erzwingt das neuste Benachrichtigungsprotokoll abhängig von der Firmwareversion. NUR EINSCHALTEN, WENN DU WEISST WAS DU TUST!</string>
<string name="pref_title_pebble_forceuntested">Ungetestete Features freischalten</string>
<string name="pref_summary_pebble_forceuntested">Schaltet ungetetestete Features frei. TU DIES NUR, WENN DU WEIßT, WAS DU TUST!</string>
<string name="pref_title_pebble_forcele">BLE immer bevorzugen</string>
<string name="pref_summary_pebble_forcele">Nutze den experimentellen LE support für alle Pebbles anstelle von BT classic. Setzt voraus, dass die \"Pebble LE\" gepaart wird, nachdem die nicht-LE Pebble einmal verbunden war.</string>
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU Limit</string>
<string name="pref_summary_pebble_mtu_limit">Wenn deine Pebble 2/Pebble LE nicht so wie erwartet funktioniert, versuche die MTU zu begrenzen (erlaubte Werte zwischen 20512)</string>
<string name="pref_title_pebble_enable_applogs">Watch App Logging einschalten</string>
<string name="pref_summary_pebble_enable_applogs">Schreibt logs von Watch Apps in Gadgetbridge logs (Pebble muss nach Ändern der Option erneut verbunden werden)</string>
<string name="pref_title_pebble_reconnect_attempts">Neuverbindungsversuche</string>
<string name="not_connected">nicht verbunden</string>
<string name="connecting">verbinde</string>
@ -126,7 +137,7 @@
<string name="title_activity_android_pairing">Gerät paaren</string>
<string name="android_pairing_hint">Verwende den Android Bluetooth Paaren-Dialog um Dein Gerät zu paaren.</string>
<string name="title_activity_mi_band_pairing">Paare Dein Mi Band</string>
<string name="pairing">Paarung mit %s…</string>
<string name="pairing">Pairing mit %s…</string>
<string name="message_cannot_pair_no_mac">Kein MAC Adresse bekommen, kann nicht paaren.</string>
<string name="preferences_category_device_specific_settings">Gerätespezifische Einstellungen</string>
<string name="preferences_miband_settings">Mi Band Einstellungen</string>
@ -297,4 +308,5 @@ Wenn Du schon deine Daten importiert hast und mit dem Ergebnis zufrieden bist, k
<string name="Delete">Löschen</string>
<!--Strings related to Vibration Activity-->
<string name="title_activity_vibration">Vibration</string>
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -12,7 +12,7 @@
<string name="controlcenter_disconnect">Desconectar</string>
<string name="controlcenter_delete_device">Borrar Dispositivo</string>
<string name="controlcenter_delete_device_name">Borrar %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">¡Esta acción borrará el dispositivo y toda la información asociada a él!</string>
<string name="controlcenter_delete_device_dialogmessage">¡Esta acción borrará el dispositivo y toda su información asociada!</string>
<string name="title_activity_debug">Depuración</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestor de app</string>
@ -27,6 +27,8 @@
<string name="appmanager_health_deactivate">Desactivar</string>
<string name="appmanager_hrm_activate">Activar Monitor de Ritmo Cardíaco</string>
<string name="appmanager_hrm_deactivate">Desactivar Monitor de Ritmo Cardíaco</string>
<string name="appmanager_weather_activate">Activar la aplicación del tiempo del sistema</string>
<string name="appmanager_weather_deactivate">Desactivar la aplicación del tiempo del sistema</string>
<string name="app_configure">Configurar</string>
<string name="app_move_to_top">Mover a la parte de arriba</string>
<!--Strings related to AppBlacklist-->
@ -36,14 +38,14 @@
<string name="fw_upgrade_notice">Estás a punto de instalar el firmware %s en lugar del que está en tu MiBand.</string>
<string name="fw_multi_upgrade_notice">Estás a punto de instalar los firmwares %1$s y %2$s en lugar de los que están en tu MiBand.</string>
<string name="miband_firmware_known">Este firmware ha sido probado y se sabe que es compatible con Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">Este firmware no ha sido probado y puede que no sea compatible con Gadgetbridge.\n\nNO se recomienda la instalación en tu MiBand!.</string>
<string name="miband_firmware_suggest_whitelist">Si aún así quieres seguir y las cosas continúan funcionando correctamente después de esto, por favor indícales a los desarrolladores de Gadgetbridge que la versión del firmware: %s funciona bien.</string>
<string name="miband_firmware_unknown_warning">Este firmware no ha sido probado y puede que no sea compatible con Gadgetbridge.\n\n¡NO se recomienda la instalación en tu MiBand!.</string>
<string name="miband_firmware_suggest_whitelist">Si aun así quieres seguir y las cosas continúan funcionando correctamente, por favor indícales a los desarrolladores de Gadgetbridge que esta versión del firmware funciona bien: %s .</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Ajustes</string>
<string name="pref_header_general">Ajustes generales</string>
<string name="pref_title_general_autoconnectonbluetooth">Conectarse al dispositivo cuando el Bluetooth esté activado</string>
<string name="pref_title_general_autocreonnect">Reconectar automáticamente</string>
<string name="pref_title_audo_player">Reproductor de audio preferido</string>
<string name="pref_title_audo_player">Reproductor de audio favorito</string>
<string name="pref_default">Predeterminado</string>
<string name="pref_header_datetime">Fecha y hora</string>
<string name="pref_title_datetime_syctimeonconnect">Sincronizar hora</string>
@ -62,7 +64,7 @@
<string name="pref_title_notifications_generic">Soporte para notificaciones genéricas</string>
<string name="pref_title_whenscreenon">… también con pantalla encendida</string>
<string name="pref_title_notification_filter">No Molestar</string>
<string name="pref_summary_notification_filter">Dejar de enviar Notificaciones no deseadas basándose en el modo No Molestar</string>
<string name="pref_summary_notification_filter">Dejar de enviar notificaciones no deseadas basándose en el modo No Molestar</string>
<string name="always">siempre</string>
<string name="when_screen_off">cuando la pantalla está apagada</string>
<string name="never">nunca</string>
@ -98,6 +100,8 @@
<string name="pref_summary_pebble_forceuntested">Habilita características que no han sido probadas. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO!</string>
<string name="pref_title_pebble_forcele">Preferir siempre BLE</string>
<string name="pref_summary_pebble_forcele">Usar el soporte experimental de Pebble LE para todos los Pebble en lugar del bluetooth clásico. Requiere vincular \"Pebble LE\" si un Pebble no-LE ha sido vinculado antes.</string>
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE límite de GATT MTU</string>
<string name="pref_summary_pebble_mtu_limit">Si tu Pebble 2/Pebble LE no funciona correctamente, prueba esta opción para limitar el MTU (rango válido 20512)</string>
<string name="pref_title_pebble_enable_applogs">Activar crear registros de la App del Reloj</string>
<string name="pref_summary_pebble_enable_applogs">Producirá registros de las apps del reloj que Gadgetbridge guardará (necesita reconexión)</string>
<string name="pref_title_pebble_reconnect_attempts">Intentos de reconexión</string>
@ -168,7 +172,7 @@
<string name="vibration_profile_medium">Medio</string>
<string name="vibration_profile_long">Largo</string>
<string name="vibration_profile_waterdrop">Muy largo</string>
<string name="vibration_profile_ring">Ring</string>
<string name="vibration_profile_ring">Timbre</string>
<string name="vibration_profile_alarm_clock">Alarma</string>
<string name="miband_prefs_vibration">Vibración</string>
<string name="vibration_try">Probar</string>
@ -206,7 +210,7 @@
<string name="pbw_install_handler_unable_to_install">No se ha podido instalar el fichero: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">No se puede instalar este firmware: no coincide con la revision hardware de tu Pebble.</string>
<string name="installer_activity_wait_while_determining_status">Por favor, espera mientras se determina el estado de la instalación...</string>
<string name="notif_battery_low_title">Batería baja del Gadget!</string>
<string name="notif_battery_low_title">¡Batería baja del Gadget!</string>
<string name="notif_battery_low_percent">A %1$s le queda: %2$s%% batería</string>
<string name="notif_battery_low_bigtext_last_charge_time">Última carga: %s \n</string>
<string name="notif_battery_low_bigtext_number_of_charges">Número de cargas: %s</string>
@ -217,13 +221,13 @@
<string name="fwapp_install_device_not_ready">El archivo no puede ser instalado, el dispositivo no está listo.</string>
<string name="miband_installhandler_miband_firmware">Miband firmware %1$s</string>
<string name="miband_fwinstaller_compatible_version">Versión compatible</string>
<string name="miband_fwinstaller_untested_version">Versión no probada!</string>
<string name="miband_fwinstaller_untested_version">¡Versión no probada!</string>
<string name="fwappinstaller_connection_state">Conexión al dispositivo: %1$s</string>
<string name="pbw_installhandler_pebble_firmware">Pebble firmware %1$s</string>
<string name="pbwinstallhandler_correct_hw_revision">Revisión de hardware correcta</string>
<string name="pbwinstallhandler_incorrect_hw_revision">La revisión de hardware es incorrecta!</string>
<string name="pbwinstallhandler_incorrect_hw_revision">¡La versión de hardware es incorrecta!</string>
<string name="pbwinstallhandler_app_item">%1$s (%2$s)</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Hubo un problema con la transferencia de firmware. NO REINICIES tu Mi Band!</string>
<string name="updatefirmwareoperation_updateproblem_do_not_reboot">Hubo un problema con la transferencia de firmware. ¡NO REINICIES tu Mi Band!</string>
<string name="updatefirmwareoperation_metadata_updateproblem">Hubo un problema con la transferencia de metadatos del firmware</string>
<string name="updatefirmwareoperation_update_complete">Instalación del firmware completa</string>
<string name="updatefirmwareoperation_update_complete_rebooting">Instalación del firmware completa, reiniciando dispositivo...</string>
@ -282,7 +286,7 @@
<string name="title_activity_onboarding">Importar Base de Datos</string>
<string name="import_old_db_buttonlabel">Importar datos de actividad antiguos</string>
<string name="import_old_db_information">Desde Gadgetbridge 0.12.0 usamos un nuevo formato de base de datos.
Se pueden importar los antiguos datos de actividad y asociarlos con el dispoditivo al que se está conectando (%1$s).\n
Se pueden importar los antiguos datos de actividad y asociarlos con el dispositivo al que se está conectando (%1$s).\n
\n
Si no importas los antiguos datos de actividad ahora, siempre lo podrás hacer después seleccionando \"MERGE OLD ACTIVITY DATA\" en el apartado de gestión de base de datos de actividad.\n
\n
@ -301,17 +305,17 @@ Por favor, ten en cuenta que puedes importar datos desde Mi Band, Pebble Health
<string name="dbmanagementactivity_overwrite_database_confirmation">¿Quiere sobreescribir la base de datos actual? Todos sus datos actuales (si los hay) se borrarán.</string>
<string name="dbmanagementactivity_import_successful">Importado con éxito.</string>
<string name="dbmanagementactivity_error_importing_db">Error importando DB: %1$s</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">No se ha encontrado una Base de Datos con actividad antigua, no se importará nada.</string>
<string name="dbmanagementactivity_no_connected_device">No hay ningún dispositivo conectado al que asociar la Base de Datos antigua.</string>
<string name="dbmanagementactivity_merging_activity_data_title">Uniendo los datos de actividad</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">No se ha encontrado una base de datos con actividad antigua, no se importará nada.</string>
<string name="dbmanagementactivity_no_connected_device">No hay ningún dispositivo conectado al que asociar la base de datos antigua.</string>
<string name="dbmanagementactivity_merging_activity_data_title">Fusionando los datos de actividad</string>
<string name="dbmanagementactivity_please_wait_while_merging">Por favor, espere mientras se unen las bases de datos.</string>
<string name="dbmanagementactivity_error_importing_old_activity_data">Error importando los datos antiguos a la nueva Base de Datos.</string>
<string name="dbmanagementactivity_error_importing_old_activity_data">Error importando los datos antiguos a la nueva base de datos.</string>
<string name="dbmanagementactivity_associate_old_data_with_device">Asociar los datos antiguos al dispositivo</string>
<string name="dbmanagementactivity_delete_activity_data_title">¿Quiere borrar los datos de actividad?</string>
<string name="dbmanagementactivity_really_delete_entire_db">¿Quiere borrar la base de datos? Todos sus datos de actividad y la información sobre sus dispositivos se borrarán.</string>
<string name="dbmanagementactivity_delete_activity_data_title">¿Quieres borrar los datos de actividad?</string>
<string name="dbmanagementactivity_really_delete_entire_db">¿Quieres borrar la base de datos? Todos tus datos de actividad y la información sobre tus dispositivos se borrarán.</string>
<string name="dbmanagementactivity_database_successfully_deleted">Datos borrados.</string>
<string name="dbmanagementactivity_db_deletion_failed">El borrado de la Base de Datos ha fallado.</string>
<string name="dbmanagementactivity_delete_old_activity_db">¿Quieres borrar la antigua Base de Datos de Actividades?</string>
<string name="dbmanagementactivity_db_deletion_failed">El borrado de la base de datos ha fallado.</string>
<string name="dbmanagementactivity_delete_old_activity_db">¿Quieres borrar la antigua base de datos de actividades?</string>
<string name="dbmanagementactivity_delete_old_activitydb_confirmation">¿Quiere borrar la base de datos antigua? Si los datos no se han importado, se perderán.</string>
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Los datos antiguos han sido borrados.</string>
<string name="dbmanagementactivity_old_activity_db_deletion_failed">El borrado de la Base de datos antiguos ha fallado.</string>
@ -322,5 +326,5 @@ Por favor, ten en cuenta que puedes importar datos desde Mi Band, Pebble Health
<string name="title_activity_vibration">Vibración</string>
<!--Strings related to Pebble Pairing Activity-->
<string name="title_activity_pebble_pairing">Emparejando con Pebble</string>
<string name="pebble_pairing_hint">En su dispositivo Android va a aparecer un mensaje para emparejarse. Si no apareciera, mire en el cajón de notificaciones y acepte la propuesta de emparejamiento. Después acepte también en su Pebble.</string>
<string name="pebble_pairing_hint">En su dispositivo Android va a aparecer un mensaje para emparejarse. Si no apareciera, mira en el cajón de notificaciones y acepta la propuesta de emparejamiento. Después acepta también en tu Pebble.</string>
</resources>

View File

@ -6,12 +6,13 @@
<string name="action_debug">Déboguer</string>
<string name="action_quit">Quitter</string>
<string name="controlcenter_fetch_activity_data">Synchroniser</string>
<string name="controlcenter_start_sleepmonitor">Moniteur de sommeil (ALPHA)</string>
<string name="controlcenter_find_device">Trouver l\'appareil</string>
<string name="controlcenter_start_sleepmonitor">Suivi du sommeil (ALPHA)</string>
<string name="controlcenter_find_device">Retrouver un appareil perdu...</string>
<string name="controlcenter_take_screenshot">Prendre une capture d\'écran</string>
<string name="controlcenter_disconnect">Déconnexion</string>
<string name="controlcenter_delete_device">Supprimer l\'appareil</string>
<string name="controlcenter_delete_device_name">Supprimer %1$s</string>
<string name="controlcenter_delete_device">Supprimer lappareil</string>
<string name="controlcenter_delete_device_name">Supprimer %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">Ceci va supprimer lappareil et toutes les données associées !</string>
<string name="title_activity_debug">Déboguer</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestionnaire d\'application</string>
@ -21,8 +22,11 @@
<string name="appmananger_app_delete">Supprimer</string>
<string name="appmananger_app_delete_cache">Supprimer et effacer du cache</string>
<string name="appmananger_app_reinstall">Réinstaller</string>
<string name="appmanager_app_openinstore">Rechercher dans le magasin dapplication Pebble</string>
<string name="appmanager_health_activate">Activer</string>
<string name="appmanager_health_deactivate">Désactiver</string>
<string name="appmanager_hrm_activate">Activer la mesure du rythme cardiaque</string>
<string name="appmanager_hrm_deactivate">Désactiver la mesure du rythme cardiaque</string>
<string name="app_configure">Configurer</string>
<string name="app_move_to_top">Haut de page </string>
<!--Strings related to AppBlacklist-->
@ -32,7 +36,7 @@
<string name="fw_upgrade_notice">Vous êtes sur le point d\'installer le micrologiciel %s à la place de celui qui est actuellement sur votre Mi Band.</string>
<string name="fw_multi_upgrade_notice">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.</string>
<string name="miband_firmware_known">Ce micrologiciel a été testé et est connu pour être compatible avec Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">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.</string>
<string name="miband_firmware_unknown_warning">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.</string>
<string name="miband_firmware_suggest_whitelist">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</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Paramètres</string>
@ -51,47 +55,53 @@
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_notifications_repetitions">Répétitions</string>
<string name="pref_title_notifications_call">Appels téléphoniques</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_sms">Textos</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Messages Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Support des applications qui envoient des notification à Pebble par PebbleKit.</string>
<string name="pref_title_notifications_generic">Support des notifications génériques</string>
<string name="pref_title_whenscreenon">... y compris quand l\'écran est allumé</string>
<string name="pref_title_notification_filter">Ne Pas Déranger</string>
<string name="pref_summary_notification_filter">Arrêter lenvoie des Notification non-désirée en mode Ne Pas Déranger.</string>
<string name="pref_summary_notification_filter">Arrêter lenvoi des notifications non-désirées en mode Ne Pas Déranger.</string>
<string name="always">toujours</string>
<string name="when_screen_off">quand l\'écran est éteint</string>
<string name="never">jamais</string>
<string name="pref_blacklist">Applications bloquées</string>
<string name="pref_header_cannned_messages">Modèles de messages</string>
<string name="pref_title_canned_replies">Réponses</string>
<string name="pref_title_canned_reply_suffix">Suffixe commun</string>
<string name="pref_title_canned_reply_suffix">Suffixe fréquent</string>
<string name="pref_title_canned_messages_dismisscall">Raccrocher</string>
<string name="pref_title_canned_messages_set">Mise à jour Pebble</string>
<string name="pref_header_development">Options développeur</string>
<string name="pref_title_development_miaddr">Adresse Mi Band</string>
<string name="pref_title_pebble_settings">Paramètres Pebble</string>
<string name="pref_header_activitytrackers">Traqueur d\'activité</string>
<string name="pref_header_activitytrackers">Traqueurs d\'activité</string>
<string name="pref_title_pebble_activitytracker">Traqueur d\'activité préféré</string>
<string name="pref_title_pebble_sync_health">Synchroniser Pebble Health</string>
<string name="pref_title_pebble_sync_misfit">Synchroniser Misfit</string>
<string name="pref_title_pebble_sync_morpheuz">Synchroniser Morpheuz</string>
<string name="pref_title_enable_pebblekit">Permettre l\'accès d\'applications tierces Android</string>
<string name="pref_title_enable_pebblekit">Permettre l\'accès aux applications tierces Android</string>
<string name="pref_summary_enable_pebblekit">Activer le support expérimental pour les applications Android utilisant PebbleKit</string>
<string name="pref_title_sunrise_sunset">Lever et coucher de soleil</string>
<string name="pref_summary_sunrise_sunset">Envoyer heures de lever et coucher du soleil dans la timeline Pebble en fonction de l\'emplacement</string>
<string name="pref_summary_sunrise_sunset">Envoyer les heures de lever et coucher du soleil dans lhistorique Pebble en fonction de l\'emplacement</string>
<string name="pref_header_location">Emplacement</string>
<string name="pref_title_location_aquire">Obtenir l\'emplacement</string>
<string name="pref_title_location_latitude">Latitude</string>
<string name="pref_title_location_longitude">Longitude</string>
<string name="pref_title_location_keep_uptodate">Garder l\'emplacement à jour</string>
<string name="pref_summary_location_keep_uptodate">Tenter d\'obtenir la localisation pendant la course à pied, utiliser la localisation enregistré en cas de problème.</string>
<string name="toast_enable_networklocationprovider">Activez la localisation réseau s\'il vous plaît</string>
<string name="pref_title_location_keep_uptodate">Garder lemplacement à jour</string>
<string name="pref_summary_location_keep_uptodate">Essayer de garder la localisation à jour pendant le fonctionnement, sinon utiliser lemplacement enregistré.</string>
<string name="toast_enable_networklocationprovider">Veuillez activer la localisation réseau</string>
<string name="toast_aqurired_networklocation">Emplacement obtenu</string>
<string name="pref_title_pebble_forceprotocol">Protocole des notifications en vigueur</string>
<string name="pref_title_pebble_forceprotocol">Forcer le protocole de notification</string>
<string name="pref_summary_pebble_forceprotocol">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!</string>
<string name="pref_title_pebble_forceuntested">Activer les fonctionnalités non-testées</string>
<string name="pref_summary_pebble_forceuntested">Activer les fonctionnalités non-testées. ACTIVEZ UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES!</string>
<string name="pref_title_pebble_forcele">Toujours préférer le BLE</string>
<string name="pref_summary_pebble_forcele">Utiliser le support expérimental du LE pour toutes les Pebble au lieu du Bluetooth classique ; cela requiert lappairage d\'une \"Pebble LE\" après quune non-LE ai déjà été connectée</string>
<string name="pref_title_pebble_mtu_limit">Limite du GATT MTU de Pebble 2/LE</string>
<string name="pref_summary_pebble_mtu_limit">Si votre Pebble 2/LE ne fonctionne pas correctement, essayez d\'activer cette option pour limiter le MTU (plage valide 20-512)</string>
<string name="pref_title_pebble_enable_applogs">Activer les logs des Watch App</string>
<string name="pref_summary_pebble_enable_applogs">Ceci permettra à Gadgetbridge de conserver les logs des Watch App (requiert une reconnexion)</string>
<string name="pref_title_pebble_reconnect_attempts">Tentatives de reconnexion</string>
<string name="not_connected">non connecté</string>
<string name="connecting">connexion en cours</string>
@ -106,29 +116,33 @@
<string name="bluetooth_is_not_supported_">Le Bluetooth n\'est pas supporté.</string>
<string name="bluetooth_is_disabled_">Le Bluetooth est désactivé.</string>
<string name="tap_connected_device_for_app_mananger">Cliquez sur l\'appareil pour ouvrir le gestionnaire d\'application</string>
<string name="tap_connected_device_for_activity">Cliquez sur l\'appareil pour ouvrir l\'activité</string>
<string name="tap_connected_device_for_activity">Cliquez sur l\'appareil pour ouvrir le gestionnaire dactivité</string>
<string name="tap_connected_device_for_vibration">Cliquez sur connecter pour envoyer une vibration</string>
<string name="tap_a_device_to_connect">Tapotter sur le périphérique pour le connecter.</string>
<string name="cannot_connect_bt_address_invalid_">Connexion impossible. Ladresse Bluetooth est-elle invalide? </string>
<string name="cannot_connect_bt_address_invalid_">Connexion impossible. Ladresse Bluetooth est-elle valide? </string>
<string name="gadgetbridge_running">Gadgetbridge est en fonctionnement</string>
<string name="installing_binary_d_d">Installation du binaire %1$d/%2$d</string>
<string name="installation_failed_">échec d\'installation!</string>
<string name="installation_successful">Installation réalisé</string>
<string name="installing_binary_d_d">Installation du fichier %1$d/%2$d</string>
<string name="installation_failed_">échec de l\'installation !</string>
<string name="installation_successful">Installation réalisée avec succès</string>
<string name="firmware_install_warning">VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROCÉDEZ À VOS PROPRES RISQUES.\n\n\nCe micrologiciel est pour la version de matériel: %s</string>
<string name="app_install_info">Vous êtes sur le point d\'installer l\'application suivante:\n\n\n%1$s Version %2$s par %3$s\n</string>
<string name="n_a">N.D.</string>
<string name="initialized">Initialisé</string>
<string name="appversion_by_creator">%1$s par %2$s</string>
<string name="title_activity_discovery">Découvrir les appareils</string>
<string name="discovery_stop_scanning">Arrêter le balayage</string>
<string name="discovery_start_scanning">Démarrer le balayage</string>
<string name="title_activity_discovery">Scanner les appareils</string>
<string name="discovery_stop_scanning">Arrêter le scan</string>
<string name="discovery_start_scanning">Démarrer le scan</string>
<string name="action_discover">Connecter un nouvel appareil</string>
<string name="device_with_rssi">%1$s (%2$s)</string>
<string name="title_activity_android_pairing">Coupler l\'appareil</string>
<string name="android_pairing_hint">Utiliser le couplement Bluetooth d\'Android pour coupler l\'appareil</string>
<string name="title_activity_mi_band_pairing">Coupler votre Mi Band</string>
<string name="pairing">Coupler avec %s...</string>
<string name="message_cannot_pair_no_mac">Aucune adresse mac fournie, ne peut être couplé</string>
<string name="title_activity_android_pairing">Appairer l\'appareil</string>
<string name="android_pairing_hint">Utiliser lappairage Bluetooth d\'Android pour appairer l\'appareil</string>
<string name="title_activity_mi_band_pairing">Appairer votre Mi Band</string>
<string name="pairing">Appairage avec %s...</string>
<string name="pairing_creating_bond_with">Création dun lien avec %1$s (%2$s)</string>
<string name="pairing_unable_to_pair_with">Impossible se sappairer avec %1$s (%2$s)</string>
<string name="pairing_in_progress">Création du lien en cours : %1$s (%2$s)</string>
<string name="pairing_already_bonded">Déjà lié avec %1$s (%2$s), connexion...</string>
<string name="message_cannot_pair_no_mac">Aucune adresse MAC fournie, ne peut être appairé</string>
<string name="preferences_category_device_specific_settings">Paramètres spécifiques à l\'appareil </string>
<string name="preferences_miband_settings">Paramètres Mi Band</string>
<string name="male">Homme</string>
@ -139,7 +153,7 @@
<string name="miband_pairing_using_dummy_userdata">Aucune donnée utilisateur valide fournie, utilisation de données fictives pour le moment</string>
<string name="miband_pairing_tap_hint">Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affilée.</string>
<string name="appinstaller_install">Installer</string>
<string name="discovery_connected_devices_hint">Rendre votre montre découvrable. Pour l\'instant les appareils connectés ne seront probablement pas découvert. Si votre montre napparaît pas après 2 minutes, essayer à nouveau après avoir redémarré.</string>
<string name="discovery_connected_devices_hint">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.</string>
<string name="discovery_note">Note:</string>
<string name="candidate_item_device_image">Image de l\'appareil</string>
<string name="miband_prefs_alias">Nom/Pseudo</string>
@ -149,7 +163,7 @@
<string name="initializing">Initialisation</string>
<string name="busy_task_fetch_activity_data">Récupération des données d\'activité</string>
<string name="sleep_activity_date_range">De %1$s à %2$s</string>
<string name="miband_prefs_wearside">Côté de port du bracelet</string>
<string name="miband_prefs_wearside">Port main gauche ou droite?</string>
<string name="pref_screen_vibration_profile">Profil de vibration</string>
<string name="vibration_profile_staccato">Saccadé</string>
<string name="vibration_profile_short">Court</string>
@ -159,15 +173,15 @@
<string name="vibration_profile_ring">Sonnette</string>
<string name="vibration_profile_alarm_clock">Réveil</string>
<string name="miband_prefs_vibration">Vibration</string>
<string name="pref_screen_notification_profile_sms">Notification SMS</string>
<string name="pref_screen_notification_profile_generic_chat">Clavardage (Chat)</string>
<string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
<string name="pref_screen_notification_profile_generic_social">Réseaux sociaux</string>
<string name="vibration_try">Essayer</string>
<string name="pref_screen_notification_profile_sms">Notification Texto</string>
<string name="pref_header_vibration_settings">Paramètres des vibrations</string>
<string name="pref_screen_notification_profile_generic">Notification générique</string>
<string name="pref_screen_notification_profile_pebblemsg">Notification Pebble</string>
<string name="pref_screen_notification_profile_email">Notification e-mail</string>
<string name="pref_screen_notification_profile_email">Notification des Mails</string>
<string name="pref_screen_notification_profile_incoming_call">Notification d\'appels entrants</string>
<string name="pref_screen_notification_profile_generic_chat">Tchat</string>
<string name="pref_screen_notification_profile_generic_navigation">Navigation</string>
<string name="pref_screen_notification_profile_generic_social">Réseau social</string>
<string name="control_center_find_lost_device">Trouver l\'appareil perdu</string>
<string name="control_center_cancel_to_stop_vibration">Annuler pour arrêter les vibrations</string>
<string name="title_activity_charts">Votre activité</string>
@ -193,7 +207,7 @@
<string name="installer_activity_unable_to_find_handler">Impossible de trouver un gestionnaire pour installer ce fichier.</string>
<string name="pbw_install_handler_unable_to_install">Impossible d\'installer le ficher suivant: %1$s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Impossible d\'installer le micrologiciel spécifié: il ne correspond pas à la version du matériel de votre Pebble.</string>
<string name="installer_activity_wait_while_determining_status">S\'il vous plait patientez pendant la détermination de l\'état de l\'installation...</string>
<string name="installer_activity_wait_while_determining_status">Veuillez patienter pendant la détermination de l\'état de l\'installation...</string>
<string name="notif_battery_low_title">Niveau de batterie faible!</string>
<string name="notif_battery_low_percent">%1$s batterie restante: %2$s%%</string>
<string name="notif_battery_low_bigtext_last_charge_time">Dernière charge: %s \n</string>
@ -218,7 +232,7 @@
<string name="updatefirmwareoperation_write_failed">Échec lors de l\'écriture du micrologiciel</string>
<string name="chart_steps">Pas</string>
<string name="liveactivity_live_activity">Activité en direct</string>
<string name="weeksteps_today_steps_description">Nombre de pas aujourd\'hui, objectif : %1$s</string>
<string name="weeksteps_today_steps_description">Nombre de pas aujourd\'hui, objectif: %1$s</string>
<string name="pref_title_dont_ack_transfer">Ne pas confirmer le transfert de données d\'activités</string>
<string name="pref_summary_dont_ack_transfers">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.</string>
<string name="pref_summary_keep_data_on_device">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.</string>
@ -241,11 +255,11 @@
<string name="miband_prefs_reserve_alarm_calendar">Alarmes à réserver pour événements futurs</string>
<string name="miband_prefs_hr_sleep_detection">Utiliser le capteur cardiaque pour améliorer la précision du sommeil</string>
<string name="miband_prefs_device_time_offset_hours">La compensation de temps en heure (pour détecter le sommeil de travailleurs en rotation, par exemple)</string>
<string name="miband2_prefs_dateformat">Mi band 2 : format de l\'heure</string>
<string name="dateformat_time">Heure seule</string>
<string name="dateformat_date_time"><![CDATA[Heure & date]]></string>
<string name="mi2_prefs_activate_display_on_lift">Allumer quand le poignet se lève</string>
<string name="FetchActivityOperation_about_to_transfer_since">About to transfer data since %1$s</string>
<string name="miband2_prefs_dateformat">Mi2 : Format de la date</string>
<string name="dateformat_time">Heure</string>
<string name="dateformat_date_time"><![CDATA[Time & Date]]></string>
<string name="mi2_prefs_activate_display_on_lift">Allumer l\'écran lors d\'un mouvement</string>
<string name="FetchActivityOperation_about_to_transfer_since">Sur le point de transférer des données depuis %1$s</string>
<string name="waiting_for_reconnect">en attente de reconnexion</string>
<string name="activity_prefs_about_you">A propos de vous</string>
<string name="activity_prefs_year_birth">Année de naissance</string>
@ -256,7 +270,7 @@
<string name="authentication_required">authentification requise</string>
<string name="appwidget_text">ZzZz</string>
<string name="add_widget">Ajouter un widget</string>
<string name="activity_prefs_sleep_duration">Durée préférée de sommeil en heures</string>
<string name="activity_prefs_sleep_duration">Préférer le mode heure pendant le sommeil</string>
<string name="appwidget_alarms_set">Une alarme a été enregistré pour %1$02d:%2$02d</string>
<string name="device_hw">Modèle: %1$s</string>
<string name="device_fw">Micrologiciel: %1$s</string>
@ -304,11 +318,14 @@ Si vous avez déjà importé vos données et êtes satisfait du résultat, vous
<string name="dbmanagementactivity_db_deletion_failed">Échec de la destruction de la base de donnée.</string>
<string name="dbmanagementactivity_delete_old_activity_db">Voulez vous détruire les anciennes activités de la base ?</string>
<string name="dbmanagementactivity_delete_old_activitydb_confirmation">Voulez vous vraiment détruire entièrement la base de donnée ? Toutes vos données non importé seront perdu.</string>
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les ancienne données d\'activité ont été effacés correctement.</string>
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les anciennes données d\'activité ont été effacées correctement.</string>
<string name="dbmanagementactivity_old_activity_db_deletion_failed">Échec de la destruction de l\'ancienne base de donnée.</string>
<string name="dbmanagementactivity_overwrite">Écraser</string>
<string name="Cancel">Annuler</string>
<string name="Delete">Supprimer</string>
<!--Strings related to Vibration Activity-->
<string name="title_activity_vibration">Vibration</string>
<!--Strings related to Pebble Pairing Activity-->
<string name="title_activity_pebble_pairing">Appairage avec une Pebble</string>
<string name="pebble_pairing_hint">Une fenêtre dappairage va safficher sur votre téléphone. Si cela ne se produit pas, regardez dans vos notifications et acceptez la demande dappairage. Acceptez ensuite la demande dappairage sur votre Pebble.</string>
</resources>

View File

@ -138,7 +138,6 @@
<string name="miband_pairing_using_dummy_userdata">Dati dell\'utente non inseriti, vengono usati dati d\'esempio.</string>
<string name="miband_pairing_tap_hint">Quando la Mi Band vibra e lampeggia, dalle qualche leggero colpetto.</string>
<string name="appinstaller_install">Installa</string>
<string name="discovery_connected_devices_hint">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.</string>
<string name="discovery_note">Nota:</string>
<string name="candidate_item_device_image">Immagine dispositivo</string>
<string name="miband_prefs_alias">Nome / Soprannome</string>
@ -299,4 +298,5 @@ Si possono importare i dati da Mi Band, Pebble Health e Morpheuz, NON quelli di
<string name="Delete">Cancella</string>
<!--Strings related to Vibration Activity-->
<string name="title_activity_vibration">Vibrazione</string>
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -27,6 +27,8 @@
<string name="appmanager_health_deactivate">非アクティベート</string>
<string name="appmanager_hrm_activate">HRM をアクティベート</string>
<string name="appmanager_hrm_deactivate">HRM を非アクティベート</string>
<string name="appmanager_weather_activate">システムの天気アプリを有効にする</string>
<string name="appmanager_weather_deactivate">システムの天気アプリを無効にする</string>
<string name="app_configure">設定</string>
<string name="app_move_to_top">先頭に移動</string>
<!--Strings related to AppBlacklist-->
@ -98,6 +100,8 @@
<string name="pref_summary_pebble_forceuntested">テストされていない機能を有効にします。何をしているかわかっている場合のみ有効にしてください!</string>
<string name="pref_title_pebble_forcele">常に BLE を好みにする</string>
<string name="pref_summary_pebble_forcele">すべてのPebbleに対して、BT クラシックではなく実験的なPebble LEサポートを使用します。非LEで一度接続された後に \"Pebble LE\" をペアリングする必要があります</string>
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU 制限</string>
<string name="pref_summary_pebble_mtu_limit">Pebble 2/Pebble LE が期待どおりに機能しない場合は、この設定を試して MTU を制限してください (有効範囲 20-512)</string>
<string name="pref_title_pebble_enable_applogs">ウォッチアプリのログ記録を有効にする</string>
<string name="pref_summary_pebble_enable_applogs">Gadgetbridgeがウォッチアプリからログを記録するようにする (再接続が必要です)</string>
<string name="pref_title_pebble_reconnect_attempts">再接続の試行</string>

View File

@ -108,7 +108,6 @@
<string name="miband_pairing_using_dummy_userdata">올바르지 않은 사용자 정보입니다. 일단 임시 사용자 정보를 사용합니다.</string>
<string name="miband_pairing_tap_hint">Mi Band가 진동하고 깜빡일 때, 연달아 몇 번 두드리세요.</string>
<string name="appinstaller_install">설치</string>
<string name="discovery_connected_devices_hint">기기를 발견 가능하도록 설정하세요. 현재 연결된 기기들은 발견될 수 없습니다. 2분이 지나도 기기가 나타나지 않는다면 재부팅 후 다시 시도해보세요.</string>
<string name="discovery_note">알림: </string>
<string name="candidate_item_device_image">기기 이미지</string>
<string name="miband_prefs_alias">이름/별명</string>
@ -131,8 +130,6 @@
<string name="pref_screen_notification_profile_sms">SMS 알림</string>
<string name="pref_header_vibration_settings">진동 설정</string>
<string name="pref_screen_notification_profile_generic">일반 알림</string>
<string name="pref_screen_notification_profile_pebblemsg">Pebble 알림</string>
<string name="pref_screen_notification_profile_email">Mail 알림</string>
<string name="pref_screen_notification_profile_incoming_call">걸려오는 전화 알림</string>
<string name="control_center_find_lost_device">잃어버린 기기 찾기</string>
<string name="control_center_cancel_to_stop_vibration">진동을 멈추려면 취소를 선택하세요.</string>
@ -226,4 +223,6 @@
<string name="live_activity_heart_rate">심박수</string>
<!--Strings related to Onboading Activity-->
<string name="Delete">삭제</string>
<!--Strings related to Vibration Activity-->
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -3,17 +3,32 @@
<string name="app_name">Gadgetbridge</string>
<string name="title_activity_controlcenter">Gadgetbridge</string>
<string name="action_settings">Ustawienia</string>
<string name="action_debug">Usuń błąd</string>
<string name="action_debug">Debuguj</string>
<string name="action_quit">Zakończ</string>
<string name="controlcenter_fetch_activity_data">Synchronizuj</string>
<string name="controlcenter_start_sleepmonitor">Monitor snu (ALPHA)</string>
<string name="controlcenter_find_device">Odnajdź zagubione urządzenie</string>
<string name="controlcenter_take_screenshot">Zrób printscreen</string>
<string name="controlcenter_take_screenshot">Zrób screena</string>
<string name="controlcenter_disconnect">Rozłącz</string>
<string name="controlcenter_delete_device">Usuń urządzenie</string>
<string name="controlcenter_delete_device_name">Usuń %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">To usunie urządzenie oraz zgromadzone dane</string>
<string name="title_activity_debug">Usuń błąd</string>
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Zarządzanie aplikacjami</string>
<string name="appmanager_cached_watchapps_watchfaces">Aplikacje w pamięci</string>
<string name="appmanager_installed_watchapps">Zainstalowane aplikacje</string>
<string name="appmanager_installed_watchfaces">Zainstalowane tarcze</string>
<string name="appmananger_app_delete">Usuń</string>
<string name="appmananger_app_delete_cache">Odinstaluj i usuń z pamięci</string>
<string name="appmananger_app_reinstall">Zainstaluj ponownie</string>
<string name="appmanager_app_openinstore">Szukaj w Pebble Appstore</string>
<string name="appmanager_health_activate">Aktywuj</string>
<string name="appmanager_health_deactivate">Deaktywuj</string>
<string name="appmanager_hrm_activate">Aktywuj HRM</string>
<string name="appmanager_hrm_deactivate">Deaktywuj HRM</string>
<string name="app_configure">Konfiguruj</string>
<string name="app_move_to_top">Przejdź do góry</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Czarna lista powiadomień</string>
<!--Strings related to FwAppInstaller-->
@ -26,11 +41,16 @@
<string name="title_activity_settings">Ustawienia</string>
<string name="pref_header_general">Ustawienia ogólne</string>
<string name="pref_title_general_autoconnectonbluetooth">Połącz z urządzeniem gdy Bluetooth jest włączone</string>
<string name="pref_title_general_autocreonnect">Łącz automatycznie</string>
<string name="pref_title_audo_player">Domyślny odtwarzacz muzyki</string>
<string name="pref_default">Domyślny</string>
<string name="pref_header_datetime">Data i godzina</string>
<string name="pref_title_datetime_syctimeonconnect">Synchronizuj czas</string>
<string name="pref_summary_datetime_syctimeonconnect">Synchronizuj czas urządzenia podczas połączenia gdy czas lub strefa czasowa zmienia się na Androidzie</string>
<string name="pref_title_theme">Motyw</string>
<string name="pref_theme_light">Jasny</string>
<string name="pref_theme_dark">Ciemny</string>
<string name="pref_title_language">Język</string>
<string name="pref_header_notifications">Powiadomienia</string>
<string name="pref_title_notifications_repetitions">Powtórzenia</string>
<string name="pref_title_notifications_call">Połączenia</string>
@ -39,19 +59,41 @@
<string name="pref_title_notifications_pebblemsg">Wiadomości Pebble</string>
<string name="pref_title_notifications_generic">Obsługa ogólnych powiadomień</string>
<string name="pref_title_whenscreenon">... także gdy ekran jest włączony</string>
<string name="pref_title_notification_filter">Nie przeszkadzaj</string>
<string name="always">zawsze</string>
<string name="when_screen_off">gdy ekran jest wyłączony</string>
<string name="never">nigdy</string>
<string name="pref_blacklist">Czarna lista aplikacji</string>
<string name="pref_header_cannned_messages">Wiadomości zwrotne</string>
<string name="pref_title_canned_replies">Odpowiedzi</string>
<string name="pref_title_canned_messages_set">Uaktualnij na Pebble</string>
<string name="pref_header_development">Ustawienia programisty</string>
<string name="pref_title_development_miaddr">Adres Mi Band</string>
<string name="pref_title_pebble_settings">Ustawienia Pebble</string>
<string name="pref_header_activitytrackers">Monitory aktywności</string>
<string name="pref_title_pebble_activitytracker">Preferowany monitor</string>
<string name="pref_title_pebble_sync_health">Synchronizuj Pebble Health</string>
<string name="pref_title_pebble_sync_misfit">Synchronizuj Misfit</string>
<string name="pref_title_pebble_sync_morpheuz">Synchronizuj Morpheuz</string>
<string name="pref_title_enable_pebblekit">Zezwól zewnętrznym aplikacjom Android na dostęp</string>
<string name="pref_summary_enable_pebblekit">Włącz eksperymentalną obsługę aplikacji android przez PebbleKit</string>
<string name="pref_title_sunrise_sunset">Wschód i zachód</string>
<string name="pref_summary_sunrise_sunset">Wyślij wschód i zachód do linii czasu Pebble bazując na lokalizacji</string>
<string name="pref_header_location">Lokalizacja</string>
<string name="pref_title_location_aquire">Otrzymaj lokalizację</string>
<string name="pref_title_location_latitude">Szerokość</string>
<string name="pref_title_location_longitude">Długość</string>
<string name="pref_title_location_keep_uptodate">Utrzymuj aktualną lokalizację</string>
<string name="toast_enable_networklocationprovider">Włącz usługę lokalizacji</string>
<string name="toast_aqurired_networklocation">Lokalizacja otrzymana</string>
<string name="pref_title_pebble_forceprotocol">Wymuś protokół komunikacji</string>
<string name="pref_summary_pebble_forceprotocol">Ta opcja wymusza użycie najnowszego protokołu powiadomień w zależności od wersji firmware. WŁĄCZ JEDYNIE JEŚLI WIESZ CO ROBISZ!</string>
<string name="pref_title_pebble_forceuntested">Włącz nietestowane funkcje</string>
<string name="pref_summary_pebble_forceuntested">Włącz nie testowane funkcje. WŁĄCZ JEDYNIE JEŚLI WIESZ CO ROBISZ!</string>
<string name="pref_title_pebble_forcele">Preferuj BLE</string>
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT limit MTU</string>
<string name="pref_summary_pebble_mtu_limit">Jeśli Twój Pebble 2/ Pebble LE nie działa jak należy spróbuj ustawić ten limit MTU (zakres 20-512)</string>
<string name="pref_title_pebble_enable_applogs">Włącz logowanie aplikacji</string>
<string name="pref_title_pebble_reconnect_attempts">Próby ponownego połączenia</string>
<string name="not_connected">nie połączony</string>
<string name="connecting">łącze</string>
@ -65,6 +107,8 @@
<string name="this_is_a_test_notification_from_gadgetbridge">To jest testowe powiadomienie z Gadgetbridge</string>
<string name="bluetooth_is_not_supported_">Bluetooth nie jest obsługiwane</string>
<string name="bluetooth_is_disabled_">Bluetooth jest wyłączone</string>
<string name="tap_connected_device_for_app_mananger">Tapnij urządzenie aby uruchomić menadżer aplikacji</string>
<string name="tap_a_device_to_connect">Dotknij urządzenie aby połączyć</string>
<string name="cannot_connect_bt_address_invalid_">Nie można połączyć. Adres BT nieprawidłowy?</string>
<string name="gadgetbridge_running">Gadgetbridge działa</string>
<string name="installing_binary_d_d">Instalowanie binarki %1$d/%2$d</string>
@ -116,9 +160,10 @@
<string name="pref_screen_notification_profile_sms">Powiadomienie SMS</string>
<string name="pref_header_vibration_settings">Ustawienia wibracji</string>
<string name="pref_screen_notification_profile_generic">Ogólne powiadomienia</string>
<string name="pref_screen_notification_profile_pebblemsg">Powiadomienia Pebble</string>
<string name="pref_screen_notification_profile_email">Powiadomienia email</string>
<string name="pref_screen_notification_profile_incoming_call">Powiadomienia o połaczeniach przychodzących</string>
<string name="pref_screen_notification_profile_generic_navigation">Nawigacja</string>
<string name="pref_screen_notification_profile_generic_social">Sieć spolecznościowa</string>
<string name="control_center_find_lost_device">Odnajdź zagubione urządzenie</string>
<string name="control_center_cancel_to_stop_vibration">Anuluj by przerwać wibracje.</string>
<string name="title_activity_charts">Twoja aktywność</string>
@ -173,6 +218,7 @@
<string name="pref_title_dont_ack_transfer">Nie wysyłaj danych aktywności</string>
<string name="pref_summary_dont_ack_transfers">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</string>
<string name="pref_summary_keep_data_on_device">Dane aktywności będą zachowane na Mi Band nawet po synchronizacji. Przydatne gdy Gadgetbridge jest używany z innymi aplikacjami.</string>
<string name="pref_summary_low_latency_fw_update">To może pomóc na urządzeniach gdzie uaktualnienie kończy się błędem</string>
<string name="live_activity_steps_history">Historia kroków</string>
<string name="live_activity_current_steps_per_minute">Aktualnie kroków/min</string>
<string name="live_activity_total_steps">Kroków łącznie</string>
@ -188,12 +234,38 @@
<string name="miband_fwinstaller_incompatible_version">Niekompatybilny firmware</string>
<string name="fwinstaller_firmware_not_compatible_to_device">Ten firmware nie jest kompatybilny z urządzeniem</string>
<string name="miband_prefs_reserve_alarm_calendar">Alarmy zarezerwowane dla nadchodzących zdarzeń</string>
<string name="miband_prefs_hr_sleep_detection">Użyj czujnika tętna alby poprawić detekcje snu</string>
<string name="miband2_prefs_dateformat">Mi2: Format daty</string>
<string name="dateformat_time">Czas</string>
<string name="waiting_for_reconnect">oczekiwanie na ponowne połaczenie</string>
<string name="activity_prefs_about_you">O tobie</string>
<string name="activity_prefs_year_birth">Data urodzenia</string>
<string name="activity_prefs_gender">Płeć</string>
<string name="activity_prefs_height_cm">Wzrost w cm</string>
<string name="activity_prefs_weight_kg">Waga w kg</string>
<string name="add_widget">Dodaj widget</string>
<string name="activity_prefs_sleep_duration">Preferowana długość snu w godzinach</string>
<string name="device_hw">Hardware: %1$s</string>
<string name="device_fw">Firmware: %1$s</string>
<string name="updatefirmwareoperation_update_in_progress">Trwa aktualizacja firmware</string>
<string name="charts_legend_heartrate">Puls</string>
<string name="live_activity_heart_rate">Puls</string>
<!--Strings related to Onboading Activity-->
<string name="title_activity_onboarding">Import bazydanych</string>
<string name="import_old_db_buttonlabel">Import starszych danych aktywności</string>
<string name="action_db_management">Zarządzanie bazą danych</string>
<string name="title_activity_db_management">Zarządzanie bazą danych</string>
<string name="activity_db_management_merge_old_title">Importuj / Usuń starą bazę danych</string>
<string name="dbmanagementactivity_exported_to">Wyeksportowano do: %1$s</string>
<string name="dbmanagementactivity_error_exporting_db">Błąd eksportu bazy: %1$s</string>
<string name="dbmanagementactivity_import_data_title">Zaimportować?</string>
<string name="dbmanagementactivity_overwrite_database_confirmation">Naprawdę chcesz napisać bazę? Wszystkie Twoje dane zostaną zastąpione.</string>
<string name="dbmanagementactivity_error_importing_db">Błąd importu bazy: %1$s</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">Nie znaleziono aktywności, nic do importu.</string>
<string name="dbmanagementactivity_overwrite">Nadpisz</string>
<string name="Cancel">Anuluj</string>
<string name="Delete">Usuń</string>
<!--Strings related to Vibration Activity-->
<string name="title_activity_vibration">Wibracje</string>
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -99,7 +99,6 @@
<string name="title_activity_android_pairing">Сопряжение устройств</string>
<string name="android_pairing_hint">Для сопряжения устройств используйте диалог Android.</string>
<string name="title_activity_mi_band_pairing">Сопряжение вашего Mi Band</string>
<string name="pairing">Сопряжение с %s…</string>
<string name="message_cannot_pair_no_mac">MAC-адрес не был передан, сопряжение не удалось.</string>
<string name="preferences_category_device_specific_settings">Настройки устройства</string>
<string name="preferences_miband_settings">Настройки Mi Band</string>
@ -133,8 +132,6 @@
<string name="pref_screen_notification_profile_sms">SMS-уведомление</string>
<string name="pref_header_vibration_settings">Настройки вибро</string>
<string name="pref_screen_notification_profile_generic">Общие уведомления</string>
<string name="pref_screen_notification_profile_pebblemsg">Уведомления Pebble</string>
<string name="pref_screen_notification_profile_email">Уведомления почты</string>
<string name="pref_screen_notification_profile_incoming_call">Уведомления о входящем звонке</string>
<string name="control_center_find_lost_device">Найти потерянное устройство</string>
<string name="control_center_cancel_to_stop_vibration">Отмените, чтобы остановить вибро</string>
@ -223,4 +220,6 @@
<string name="updatefirmwareoperation_update_in_progress">Обновление прошивки в процессе</string>
<!--Strings related to Onboading Activity-->
<string name="Delete">Удалить</string>
<!--Strings related to Vibration Activity-->
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -105,7 +105,6 @@
<string name="title_activity_android_pairing">Створення пари з пристроєм</string>
<string name="android_pairing_hint">Для створення пари із пристроєм використовуйте діалог Android.</string>
<string name="title_activity_mi_band_pairing">Створення пари із вашим Mi—Band</string>
<string name="pairing">Створення пари із %s…</string>
<string name="message_cannot_pair_no_mac">MAC-адресу не було передано, не вдалося створити пару.</string>
<string name="preferences_category_device_specific_settings">Параметри пристрою</string>
<string name="preferences_miband_settings">Параметри Mi—Band</string>
@ -138,8 +137,6 @@
<string name="pref_screen_notification_profile_sms">SMS-сповіщення</string>
<string name="pref_header_vibration_settings">Параметри вібро</string>
<string name="pref_screen_notification_profile_generic">Загальні сповіщення</string>
<string name="pref_screen_notification_profile_pebblemsg">Сповіщення Pebble</string>
<string name="pref_screen_notification_profile_email">Сповіщення пошти</string>
<string name="pref_screen_notification_profile_incoming_call">Сповіщення під час вхідного дзвінку</string>
<string name="control_center_find_lost_device">Знайти загублений пристрій</string>
<string name="control_center_cancel_to_stop_vibration">Скасуйте, аби зупинити вібро</string>
@ -220,4 +217,6 @@
<string name="device_fw">ПЗ: %1$s</string>
<!--Strings related to Onboading Activity-->
<string name="Delete">Вилучити</string>
<!--Strings related to Vibration Activity-->
<!--Strings related to Pebble Pairing Activity-->
</resources>

View File

@ -30,6 +30,8 @@
<string name="appmanager_health_deactivate">Deactivate</string>
<string name="appmanager_hrm_activate">Activate HRM</string>
<string name="appmanager_hrm_deactivate">Deactivate HRM</string>
<string name="appmanager_weather_activate">Activate system weather app</string>
<string name="appmanager_weather_deactivate">Deactivate system weather app</string>
<string name="app_configure">Configure</string>
<string name="app_move_to_top">Move to top</string>
@ -63,6 +65,10 @@
<string name="pref_title_language">Language</string>
<string name="pref_title_minimize_priority">Hide the gadgetbridge notification</string>
<string name="pref_summary_minimize_priority_off">The icon in the status bar and the notification in the lockscreen are shown</string>
<string name="pref_summary_minimize_priority_on">The icon in the status bar and the notification in the lockscreen are hidden</string>
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_notifications_repetitions">Repetitions</string>
<string name="pref_title_notifications_call">Phone Calls</string>
@ -98,6 +104,9 @@
<string name="pref_title_pebble_sync_misfit">Sync Misfit</string>
<string name="pref_title_pebble_sync_morpheuz">Sync Morpheuz</string>
<string name="pref_title_enable_outgoing_call">Support outgoing calls</string>
<string name="pref_summary_enable_outgoing_call">Disabling this will also stop the Pebble 2/LE to vibrate on outgoing calls</string>
<string name="pref_title_enable_pebblekit">Allow 3rd Party Android App Access</string>
<string name="pref_summary_enable_pebblekit">Enable experimental support for Android Apps using PebbleKit</string>
@ -120,6 +129,8 @@
<string name="pref_summary_pebble_forceuntested">Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="pref_title_pebble_forcele">Always prefer BLE</string>
<string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles instead of BT classic, requires paring a "Pebble LE" after non LE had been connected once</string>
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limit</string>
<string name="pref_summary_pebble_mtu_limit">If your Pebble 2/Pebble LE does not work as expected, try this setting to limit the MTU (valid range 20512)</string>
<string name="pref_title_pebble_enable_applogs">Enable Watch App Logging</string>
<string name="pref_summary_pebble_enable_applogs">Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect)</string>

View File

@ -1,5 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.17.0" versioncode="81">
<change>Add weather support through "Weather Notification" app</change>
<change>Pebble: Support for build-in weather system app (FW 4.x)</change>
<change>Pebble: Add weather support for various watchfaces</change>
<change>Pebble: Delete notifications that got dismissed on the phone</change>
<change>Pebble: Add option to disable call display</change>
<change>Pebble 2/LE: Improve reliablitly and transfer speed</change>
<change>Various fixes for K9 mail when using the generic notification receiver</change>
</release>
<release version="0.16.0" versioncode="80">
<change>New devices: HPlus (e.g. Zeblaze ZeBand), contributed by João Paulo Barraca</change>
<change>ZeBand: Initial support: notifications, heart rate, sleep monitoring, user configuration, date+time</change>
<change>Pebble 2: Fix Pebble Classic FW 3.x app variant being prioritized over native Pebble 2 app variant</change>
<change>Charts (Live Activity): Fix axis labels color in dark theme</change>
<change>Mi Band: Fix ginormous step count when using Live Activity</change>
<change>Mi Band: Improved performance during activity sync</change>
<change>Mi Band 2: Fix activity data missing after doing manual hr measurements or live activity</change>
<change>Support sharing firmwares/watchapps/watchfaces to Gadgetbridge</change>
<change>Support for the "Subsonic" music player (#474)</change>
</release>
<release version="0.15.2" versioncode="79">
<change>Mi Band: Fix crash with unknown notification sources</change>
</release>
<release version="0.15.1" versioncode="78">
<change>Improved handling of notifications for some apps</change>
<change>Pebble 2/LE: Add setting to limit GATT MTU for debugging broken BLE stacks</change>
<change>Mi Band 2: Display battery status</change>
</release>
<release version="0.15.0" versioncode="77">
<change>New device: Liveview</change>
<change>Liveview: initial support (set the time and receive notifications)</change>

View File

@ -30,6 +30,12 @@
android:entryValues="@array/pref_language_values"
android:defaultValue="default"
android:summary="%s" />
<CheckBoxPreference
android:defaultValue="false"
android:key="minimize_priority"
android:summaryOff="@string/pref_summary_minimize_priority_off"
android:summaryOn="@string/pref_summary_minimize_priority_on"
android:title="@string/pref_title_minimize_priority" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_datetime"
@ -146,6 +152,11 @@
android:title="@string/pref_title_pebble_settings">
<PreferenceCategory
android:title="@string/pref_header_general">
<CheckBoxPreference
android:defaultValue="true"
android:key="pebble_enable_outgoing_call"
android:summary="@string/pref_summary_enable_outgoing_call"
android:title="@string/pref_title_enable_outgoing_call" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pebble_enable_pebblekit"
@ -341,6 +352,13 @@
android:key="pebble_force_le"
android:summary="@string/pref_summary_pebble_forcele"
android:title="@string/pref_title_pebble_forcele" />
<EditTextPreference
android:inputType="number"
android:key="pebble_mtu_limit"
android:maxLength="3"
android:defaultValue="512"
android:title="@string/pref_title_pebble_mtu_limit"
android:summary="@string/pref_summary_pebble_mtu_limit" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pebble_enable_applogs"

View File

@ -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();
}

View File

@ -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<DeviceCommunicationService> serviceController;
private final DeviceCommunicationService service;
public TestDeviceService(Context context) throws Exception {
TestDeviceService(Context context) throws Exception {
super(context);
serviceController = Robolectric.buildService(DeviceCommunicationService.class, createIntent());

View File

@ -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
@ -53,6 +54,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
@ -177,4 +183,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
}

View File

@ -0,0 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import org.junit.Assert;
import org.junit.Test;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
public class MiscTest extends TestBase {
@Test
public void testGattCharacteristic() {
String desc = GattCharacteristic.lookup(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT, "xxx");
Assert.assertEquals("heart rate control point", desc);
}
}