1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-12-25 10:05:49 +01:00

Merge branch 'master' into db-refactoring

This commit is contained in:
cpfeiffer 2016-06-06 22:16:40 +02:00
commit 3b87966fe9
59 changed files with 1244 additions and 290 deletions

View File

@ -1,4 +1,21 @@
###Changelog
####Version 0.10.0
* Pebble: option to send sunrise and sunset events to timeline
* Pebble: fix problems with unknown app keys while configuring watchfaces
* Mi Band: BLE connection fixes
* Fixes for enabling logging at whithout restarting Gadgetbridge
* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
* Display device address in device info
####Version 0.9.8
* Pebble: fix more reconnnect issues
* Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health
* Pebble: option in AppManager to delete files from cache
* Pebble: enable pbw cache and watchface configuration for Firmware 2.x
* Pebble: allow enabling of Pebble Health without "untested features" being enabled
* Pebble: fix music information being messed up
* Honour "Do Not Disturb" for phone calls and SMS
####Version 0.9.7
* Pebble: hopefully fix some reconnect issues
* Mi Band: fix live activity monitoring running forever if back button pressed

View File

@ -49,7 +49,7 @@ need to create an account and transmit any of your data to the vendor's servers.
2. Start Gadgetbridge, tap on the device you want to connect to
3. To test, choose "Debug" from the menu and play around
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Getting-Started-(Pebble))
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started)
## Features (Mi Band)
@ -103,10 +103,14 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue);
just leave a comment that you're working on one to avoid duplicated work.
Please do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting.
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or manually.
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or
manually.
## Do you have further questions or feedback?
Feel free to open an issue on our issue tracker, but please:
- do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting
- use the search functionality to ensure that your questions wasn't already answered. Don't forget to check the **closed** issues as well!
- remember that this is a community project, people are contributing in their free time because they like doing so: don't take the fun away! Be kind and constructive.
## Having problems?

View File

@ -6,6 +6,8 @@ def ABORT_ON_CHECK_FAILURE=false
tasks.withType(Test) { systemProperty 'MiFirmwareDir', System.getProperty('MiFirmwareDir', null) }
// sourceSets.test.runtimeClasspath += File('src/main/assets')
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
@ -16,8 +18,8 @@ android {
targetSdkVersion 23
// note: always bump BOTH versionCode and versionName!
versionName "0.9.7"
versionCode 51
versionName "0.10.0"
versionCode 53
}
buildTypes {
release {
@ -42,6 +44,8 @@ android {
}
dependencies {
// testCompile 'ch.qos.logback:logback-classic:1.1.3'
// testCompile 'ch.qos.logback:logback-core:1.1.3'
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.9.5"
@ -54,6 +58,7 @@ dependencies {
compile 'com.github.PhilJay:MPAndroidChart:v2.2.4'
compile 'com.github.pfichtner:durationformatter:0.1.1'
compile 'de.cketti.library.changelog:ckchangelog:1.2.2'
compile 'net.e175.klaus:solarpositioning:0.0.9'
compile 'com.github.freeyourgadget:greendao:c3830951e5dd3d1e63d7bac600d5f773b81df363'
}
@ -105,7 +110,6 @@ task pmd(type: Pmd) {
}
}
task findbugs(type: FindBugs) {
ignoreFailures = !ABORT_ON_CHECK_FAILURE
effort = "default"

View File

@ -17,6 +17,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.fsck.k9.permission.READ_MESSAGES" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:name="android.hardware.bluetooth"

View File

@ -21,9 +21,6 @@ import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.util.TypedValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
@ -31,8 +28,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import nodomain.freeyourgadget.gadgetbridge.database.DBConstants;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
@ -63,14 +58,23 @@ public class GBApplication extends Application {
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
private static final int CURRENT_PREFS_VERSION = 2;
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
private static Appender<ILoggingEvent> fileLogger;
private static Prefs prefs;
private static GBPrefs gbPrefs;
private static DBHandler lockHandler;
/**
* Note: is null on Lollipop and Kitkat
*/
private static NotificationManager notificationManager;
public static final String ACTION_QUIT
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
private static Logging logging = new Logging() {
@Override
protected String createLogDirectory() throws IOException {
File dir = FileUtils.getExternalFilesDir();
return dir.getAbsolutePath();
}
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -113,11 +117,6 @@ public class GBApplication extends Application {
}
setupExceptionHandler();
// For debugging problems with the logback configuration
// LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
// StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(GBApplication.class);
setupDatabase(this);
@ -131,7 +130,7 @@ public class GBApplication extends Application {
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
if (isRunningMarshmallowOrLater()) {
notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
// for testing DB stuff
@ -139,6 +138,10 @@ public class GBApplication extends Application {
// db.close();
}
public static void setupLogging(boolean enabled) {
logging.setupLogging(enabled);
}
private void setupExceptionHandler() {
LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(handler);
@ -148,71 +151,6 @@ public class GBApplication extends Application {
return prefs.getBoolean("log_to_file", false);
}
public static void setupLogging(boolean enable) {
try {
if (fileLogger == null) {
File dir = FileUtils.getExternalFilesDir();
// used by assets/logback.xml since the location cannot be statically determined
System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
rememberFileLogger();
}
if (enable) {
startFileLogger();
} else {
stopFileLogger();
}
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
stopFileLogger();
}
}
private static void startFileLogger() {
if (fileLogger != null && !fileLogger.isStarted()) {
addFileLogger(fileLogger);
fileLogger.start();
}
}
private static void stopFileLogger() {
if (fileLogger != null && fileLogger.isStarted()) {
fileLogger.stop();
removeFileLogger(fileLogger);
}
}
private static void rememberFileLogger() {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
fileLogger = root.getAppender("FILE");
}
private static void addFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (!root.isAttached(fileLogger)) {
root.addAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error adding logger FILE appender", ex);
}
}
private static void removeFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (root.isAttached(fileLogger)) {
root.detachAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error removing logger FILE appender", ex);
}
}
private static Logger getLogger() {
return LoggerFactory.getLogger(GBApplication.class);
}
static void setupDatabase(Context context) {
// DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test-db", null);
DBOpenHelper helper = new DBOpenHelper(context, "test-db", null);
@ -285,7 +223,7 @@ public class GBApplication extends Application {
boolean exists = false;
int starred = 0;
try {
if (cursor.moveToFirst()) {
if (cursor != null && cursor.moveToFirst()) {
exists = true;
starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED));
}

View File

@ -0,0 +1,128 @@
package nodomain.freeyourgadget.gadgetbridge;
import android.util.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.util.StatusPrinter;
public abstract class Logging {
public static final String PROP_LOGFILES_DIR = "GB_LOGFILES_DIR";
private FileAppender<ILoggingEvent> fileLogger;
public void setupLogging(boolean enable) {
try {
if (fileLogger == null) {
init();
}
if (enable) {
startFileLogger();
} else {
stopFileLogger();
}
getLogger().info("Gadgetbridge version: " + BuildConfig.VERSION_NAME);
} catch (IOException ex) {
Log.e("GBApplication", "External files dir not available, cannot log to file", ex);
stopFileLogger();
}
}
public void debugLoggingConfiguration() {
// For debugging problems with the logback configuration
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// print logback's internal status
StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(Logging.class);
}
protected abstract String createLogDirectory() throws IOException;
protected void init() throws IOException {
String dir = createLogDirectory();
if (dir == null) {
throw new IllegalArgumentException("log directory must not be null");
}
// used by assets/logback.xml since the location cannot be statically determined
System.setProperty(PROP_LOGFILES_DIR, dir);
rememberFileLogger();
}
private Logger getLogger() {
return LoggerFactory.getLogger(Logging.class);
}
private void startFileLogger() {
if (fileLogger != null && !fileLogger.isStarted()) {
addFileLogger(fileLogger);
fileLogger.setLazy(false); // hack to make sure that start() actually opens the file
fileLogger.start();
}
}
private void stopFileLogger() {
if (fileLogger != null && fileLogger.isStarted()) {
fileLogger.stop();
removeFileLogger(fileLogger);
}
}
private void rememberFileLogger() {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
fileLogger = (FileAppender<ILoggingEvent>) root.getAppender("FILE");
}
private void addFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (!root.isAttached(fileLogger)) {
root.addAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error adding logger FILE appender", ex);
}
}
private void removeFileLogger(Appender<ILoggingEvent> fileLogger) {
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (root.isAttached(fileLogger)) {
root.detachAppender(fileLogger);
}
} catch (Throwable ex) {
Log.e("GBApplication", "Error removing logger FILE appender", ex);
}
}
public FileAppender<ILoggingEvent> getFileLogger() {
return fileLogger;
}
public boolean setImmediateFlush(boolean enable) {
FileAppender<ILoggingEvent> fileLogger = getFileLogger();
Encoder<ILoggingEvent> encoder = fileLogger.getEncoder();
if (encoder instanceof LayoutWrappingEncoder) {
((LayoutWrappingEncoder) encoder).setImmediateFlush(enable);
return true;
}
return false;
}
public boolean isImmediateFlush() {
FileAppender<ILoggingEvent> fileLogger = getFileLogger();
Encoder<ILoggingEvent> encoder = fileLogger.getEncoder();
if (encoder instanceof LayoutWrappingEncoder) {
return ((LayoutWrappingEncoder) encoder).isImmediateFlush();
}
return false;
}
}

View File

@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -47,7 +48,6 @@ public class AppManagerActivity extends GBActivity {
if (action.equals(GBApplication.ACTION_QUIT)) {
finish();
} else if (action.equals(ACTION_REFRESH_APPLIST)) {
appList.clear();
int appCount = intent.getIntExtra("app_count", 0);
for (Integer i = 0; i < appCount; i++) {
String appName = intent.getStringExtra("app_name" + i.toString());
@ -55,11 +55,21 @@ public class AppManagerActivity extends GBActivity {
UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString()));
GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)];
appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
boolean found = false;
for (final ListIterator<GBDeviceApp> iter = appList.listIterator(); iter.hasNext(); ) {
final GBDeviceApp app = iter.next();
if (app.getUUID().equals(uuid)) {
app.setOnDevice(true);
iter.set(app);
found = true;
break;
}
}
if (!found) {
GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType);
app.setOnDevice(true);
appList.add(app);
}
if (prefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
mGBDeviceAppAdapter.notifyDataSetChanged();
@ -76,8 +86,10 @@ public class AppManagerActivity extends GBActivity {
private List<GBDeviceApp> getSystemApps() {
List<GBDeviceApp> systemApps = new ArrayList<>();
if (prefs.getBoolean("pebble_force_untested", false)) {
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) {
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
@ -149,9 +161,7 @@ public class AppManagerActivity extends GBActivity {
appList.addAll(getCachedApps());
if (prefs.getBoolean("pebble_force_untested", false)) {
appList.addAll(getSystemApps());
}
IntentFilter filter = new IntentFilter();
filter.addAction(GBApplication.ACTION_QUIT);
@ -171,11 +181,13 @@ public class AppManagerActivity extends GBActivity {
if (!selectedApp.isInCache()) {
menu.removeItem(R.id.appmanager_app_reinstall);
menu.removeItem(R.id.appmanager_app_delete_cache);
}
if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
menu.removeItem(R.id.appmanager_health_activate);
menu.removeItem(R.id.appmanager_health_deactivate);
} else if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
}
if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) {
menu.removeItem(R.id.appmanager_app_delete);
}
if (!selectedApp.isConfigurable()) {
@ -184,10 +196,42 @@ public class AppManagerActivity extends GBActivity {
menu.setHeaderTitle(selectedApp.getName());
}
private void removeAppFromList(UUID uuid) {
for (final ListIterator<GBDeviceApp> iter = appList.listIterator(); iter.hasNext(); ) {
final GBDeviceApp app = iter.next();
if (app.getUUID().equals(uuid)) {
iter.remove();
mGBDeviceAppAdapter.notifyDataSetChanged();
break;
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.appmanager_health_deactivate:
case R.id.appmanager_app_delete_cache:
String baseName;
try {
baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID();
} catch (IOException e) {
LOG.warn("could not get external dir while trying to access pbw cache.");
return true;
}
String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"};
for (String suffix : suffixToDelete) {
File fileToDelete = new File(baseName + suffix);
if (!fileToDelete.delete()) {
LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString());
} else {
LOG.info("deleted file: " + fileToDelete.toString());
}
}
removeAppFromList(selectedApp.getUUID());
// fall through
case R.id.appmanager_app_delete:
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
return true;
@ -196,7 +240,7 @@ public class AppManagerActivity extends GBActivity {
try {
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw");
} catch (IOException e) {
LOG.warn("could not get external dir while reading pbw cache.");
LOG.warn("could not get external dir while trying to access pbw cache.");
return true;
}
GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath));

View File

@ -353,12 +353,8 @@ public class ControlCenter extends GBActivity {
}
private void launchDiscoveryActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS));
} else {
startActivity(new Intent(this, DiscoveryActivity.class));
}
}
@Override
protected void onDestroy() {

View File

@ -20,15 +20,20 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import net.e175.klaus.solarpositioning.DeltaT;
import net.e175.klaus.solarpositioning.SPA;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.GregorianCalendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
@ -36,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class DebugActivity extends GBActivity {

View File

@ -1,5 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@ -8,10 +9,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.support.v4.app.ActivityCompat;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
@ -48,6 +51,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
// continue with LE scan, if available
if (isScanning == Scanning.SCANNING_BT) {
checkAndRequestLocationPermission();
startDiscovery(Scanning.SCANNING_BTLE);
} else {
discoveryFinished();
@ -320,6 +324,12 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
adapter.startDiscovery();
}
private void checkAndRequestLocationPermission() {
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
}
private Message getPostMessage(Runnable runnable) {
Message m = Message.obtain(handler, runnable);
m.obj = runnable;

View File

@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Scanner;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -138,19 +139,36 @@ public class ExternalPebbleJSActivity extends GBActivity {
try {
JSONObject in = new JSONObject(msg);
JSONObject out = new JSONObject();
String cur_key;
String inKey, outKey;
boolean passKey = false;
for (Iterator<String> key = in.keys(); key.hasNext(); ) {
cur_key = key.next();
int pebbleAppIndex = knownKeys.optInt(cur_key);
passKey = false;
inKey = key.next();
outKey = null;
int pebbleAppIndex = knownKeys.optInt(inKey);
if (pebbleAppIndex != 0) {
Object obj = in.get(cur_key);
passKey = true;
outKey = String.valueOf(pebbleAppIndex);
} else {
//do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ )
Scanner scanner = new Scanner(inKey);
if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) {
passKey = true;
outKey = inKey;
}
}
if (passKey && outKey != null) {
Object obj = in.get(inKey);
if (obj instanceof Boolean) {
obj = ((Boolean) obj) ? "true" : "false";
}
out.put(String.valueOf(pebbleAppIndex), obj);
out.put(outKey, obj);
} else {
GB.toast("Discarded key " + cur_key + ", not found in the local configuration.", Toast.LENGTH_SHORT, GB.WARN);
GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN);
}
}
LOG.info(out.toString());
GBApplication.deviceService().onAppConfiguration(appUuid, out.toString());

View File

@ -1,15 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class GBActivity extends AppCompatActivity {
private void setLanguage(String language) {
Locale locale;
if (language.equals("default")) {
locale = Locale.getDefault();
} else {
locale = new Locale(language);
}
Configuration config = new Configuration();
config.locale = locale;
// FIXME: I have no idea what I am doing
getApplicationContext().getResources().updateConfiguration(config, getApplicationContext().getResources().getDisplayMetrics());
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (GBApplication.isDarkThemeEnabled()) {
@ -18,6 +36,9 @@ public class GBActivity extends AppCompatActivity {
setTheme(R.style.GadgetbridgeTheme);
}
Prefs prefs = GBApplication.getPrefs();
String language = prefs.getString("language", "default");
setLanguage(language);
super.onCreate(savedInstanceState);
}
}

View File

@ -1,17 +1,28 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -19,13 +30,14 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActi
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_GENDER;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_WEIGHT_KG;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_YEAR_OF_BIRTH;
public class SettingsActivity extends AbstractSettingsActivity {
private static final Logger LOG = LoggerFactory.getLogger(SettingsActivity.class);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -114,6 +126,34 @@ public class SettingsActivity extends AbstractSettingsActivity {
category.removePreference(pref);
}
pref = findPreference("location_aquire");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(SettingsActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, false);
if (provider != null) {
Location location = locationManager.getLastKnownLocation(provider);
String latitude = String.format(Locale.US, "%.6g", location.getLatitude());
String longitude = String.format(Locale.US, "%.6g", location.getLongitude());
LOG.info("got location. Lat: " + latitude + " Lng: " + longitude);
EditTextPreference pref_latitude = (EditTextPreference) findPreference("location_latitude");
EditTextPreference pref_longitude = (EditTextPreference) findPreference("location_longitude");
pref_latitude.setText(latitude);
pref_longitude.setText(longitude);
pref_latitude.setSummary(latitude);
pref_longitude.setSummary(longitude);
} else {
LOG.warn("No location provider found, did you deny location permission?");
}
return true;
}
});
// Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
@ -146,6 +186,8 @@ public class SettingsActivity extends AbstractSettingsActivity {
"pebble_emu_addr",
"pebble_emu_port",
"pebble_reconnect_attempts",
"location_latitude",
"location_longitude",
"canned_reply_suffix",
"canned_reply_1",
"canned_reply_2",

View File

@ -41,7 +41,15 @@ public class GBDeviceAppAdapter extends ArrayAdapter<GBDeviceApp> {
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
deviceAppVersionAuthorLabel.setText(getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator()));
deviceAppNameLabel.setText(deviceApp.getName());
// FIXME: replace with small icons
String appNameLabelText = deviceApp.getName();
if (deviceApp.isInCache() || deviceApp.isOnDevice()) {
appNameLabelText += " (" + (deviceApp.isInCache() ? "C" : "")
+ (deviceApp.isOnDevice() ? "D" : "") + ")";
}
deviceAppNameLabel.setText(appNameLabelText);
switch (deviceApp.getType()) {
case APP_GENERIC:
deviceImageView.setImageResource(R.drawable.ic_watchapp);

View File

@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -51,4 +52,8 @@ public interface EventHandler {
void onScreenshotReq();
void onEnableHeartRateSleepSupport(boolean enable);
void onAddCalendarEvent(CalendarEventSpec calendarEventSpec);
void onDeleteCalendarEvent(byte type, long id);
}

View File

@ -16,6 +16,7 @@ public final class MiBandConst {
public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer";
public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar";
public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
public static final String PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS = "mi_device_time_offset_hours";
public static final String ORIGIN_SMS = "sms";
@ -27,6 +28,7 @@ public final class MiBandConst {
public static final String MI_1A = "1A";
public static final String MI_1S = "1S";
public static final String MI_AMAZFIT = "Amazfit";
public static final String MI_PRO = "2";
public static int getNotificationPrefIntValue(String pref, String origin, Prefs prefs, int defaultValue) {
String key = getNotificationPrefKey(pref, origin);

View File

@ -144,6 +144,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
return location;
}
public static int getDeviceTimeOffsetHours() throws IllegalArgumentException {
Prefs prefs = GBApplication.getPrefs();
return prefs.getInt(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, 0);
}
public static boolean getHeartrateSleepSupport(String miBandAddress) throws IllegalArgumentException {
Prefs prefs = GBApplication.getPrefs();
return prefs.getBoolean(MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION, false);

View File

@ -40,6 +40,10 @@ public class MiBandDateConverter {
value[offset + 4],
value[offset + 5]);
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
if(offsetInHours != 0)
timestamp.add(Calendar.HOUR_OF_DAY,-offsetInHours);
return timestamp;
}
@ -53,6 +57,18 @@ public class MiBandDateConverter {
* @return
*/
public static byte[] calendarToRawBytes(Calendar timestamp) {
// The mi-band device currently records sleep
// only if it happens after 10pm and before 7am.
// The offset is used to trick the device to record sleep
// in non-standard hours.
// If you usually sleep, say, from 6am to 2pm, set the
// shift to -8, so at 6am the device thinks it's still 10pm
// of the day before.
int offsetInHours = MiBandCoordinator.getDeviceTimeOffsetHours();
if(offsetInHours != 0)
timestamp.add(Calendar.HOUR_OF_DAY,offsetInHours);
return new byte[]{
(byte) (timestamp.get(Calendar.YEAR) - 2000),
(byte) timestamp.get(Calendar.MONTH),

View File

@ -16,6 +16,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.OR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_PEBBLEMSG;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_SMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
@ -62,6 +63,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
PREF_MIBAND_ADDRESS,
PREF_MIBAND_FITNESS_GOAL,
PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR,
PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS,
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL),

View File

@ -129,10 +129,6 @@ public class PBWInstallHandler implements InstallHandler {
return;
}
if (!device.getFirmwareVersion().startsWith("v3")) {
return;
}
File destDir;
GBDeviceApp app = mPBWReader.getGBDeviceApp();
try {

View File

@ -0,0 +1,89 @@
package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import net.e175.klaus.solarpositioning.DeltaT;
import net.e175.klaus.solarpositioning.SPA;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class AlarmReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(AlarmReceiver.class);
public AlarmReceiver() {
Context context = GBApplication.getContext();
Intent intent = new Intent("DAILY_ALARM");
intent.setPackage(BuildConfig.APPLICATION_ID);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent("DAILY_ALARM"), 0);
AlarmManager am = (AlarmManager) (context.getSystemService(Context.ALARM_SERVICE));
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, Calendar.getInstance().getTimeInMillis() + 10000, AlarmManager.INTERVAL_DAY, pendingIntent);
}
@Override
public void onReceive(Context context, Intent intent) {
if (!GBApplication.getPrefs().getBoolean("send_sunrise_sunset", false)) {
LOG.info("won't send sunrise and sunset events (disabled in preferences)");
return;
}
LOG.info("will resend sunrise and sunset events");
final GregorianCalendar dateTimeTomorrow = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
dateTimeTomorrow.set(Calendar.HOUR, 0);
dateTimeTomorrow.set(Calendar.MINUTE, 0);
dateTimeTomorrow.set(Calendar.SECOND, 0);
dateTimeTomorrow.set(Calendar.MILLISECOND, 0);
dateTimeTomorrow.add(GregorianCalendar.DAY_OF_MONTH, 1);
/*
* rotate ids ud reuse the id from two days ago for tomorrow, this way we will have
* sunrise /sunset for 3 days while sending only sunrise/sunset per day
*/
byte id_tomorrow = (byte) ((dateTimeTomorrow.getTimeInMillis() / (1000L * 60L * 60L * 24L)) % 3);
GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNRISE, id_tomorrow);
GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_SUNSET, id_tomorrow);
Prefs prefs = GBApplication.getPrefs();
float latitude = prefs.getFloat("location_latitude", 0);
float longitude = prefs.getFloat("location_longitude", 0);
LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude);
GregorianCalendar[] sunriseTransitSetTomorrow = SPA.calculateSunriseTransitSet(dateTimeTomorrow, latitude, longitude, DeltaT.estimate(dateTimeTomorrow));
CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
calendarEventSpec.durationInSeconds = 0;
calendarEventSpec.description = null;
calendarEventSpec.type = CalendarEventSpec.TYPE_SUNRISE;
calendarEventSpec.title = "Sunrise";
if (sunriseTransitSetTomorrow[0] != null) {
calendarEventSpec.id = id_tomorrow;
calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow[0].getTimeInMillis() / 1000);
GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec);
}
calendarEventSpec.type = CalendarEventSpec.TYPE_SUNSET;
calendarEventSpec.title = "Sunset";
if (sunriseTransitSetTomorrow[2] != null) {
calendarEventSpec.id = id_tomorrow;
calendarEventSpec.timestamp = (int) (sunriseTransitSetTomorrow[2].getTimeInMillis() / 1000);
GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec);
}
}
}

View File

@ -0,0 +1,43 @@
package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class BluetoothConnectReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class);
final DeviceCommunicationService service;
public BluetoothConnectReceiver(DeviceCommunicationService service) {
this.service = service;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (!action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
return;
}
LOG.info("got connection attempt");
GBDevice gbDevice = service.getGBDevice();
if (gbDevice != null) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getAddress().equals(gbDevice.getAddress()) && gbDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
LOG.info("will connect to " + gbDevice.getName());
GBApplication.deviceService().connect();
} else {
LOG.info("won't connect to " + device.getAddress() + "(" + device.getName() + ")");
}
}
}
}

View File

@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -18,20 +19,13 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
String artist = intent.getStringExtra("artist");
String album = intent.getStringExtra("album");
String track = intent.getStringExtra("track");
/*
Bundle bundle = intent.getExtras();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
LOG.info(String.format("%s %s (%s)", key,
value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class"));
}
*/
LOG.info("Current track: " + artist + ", " + album + ", " + track);
MusicSpec musicSpec = new MusicSpec();
musicSpec.artist = artist;
musicSpec.artist = album;
musicSpec.artist = track;
musicSpec.album = album;
musicSpec.track = track;
GBApplication.deviceService().onSetMusicInfo(musicSpec);
}

View File

@ -42,6 +42,7 @@ public class GBDevice implements Parcelable {
public static final String EXTRA_DEVICE = "device";
private static final String DEVINFO_HW_VER = "HW: ";
private static final String DEVINFO_FW_VER = "FW: ";
private static final String DEVINFO_ADDR = "ADDR: ";
private final String mName;
private final String mAddress;
private final DeviceType mDeviceType;
@ -355,6 +356,9 @@ public class GBDevice implements Parcelable {
if (mFirmwareVersion != null) {
result.add(new GenericItem(DEVINFO_FW_VER, mFirmwareVersion));
}
if (mAddress != null) {
result.add(new GenericItem(DEVINFO_ADDR, mAddress));
}
Collections.sort(result);
return result;
}

View File

@ -12,6 +12,7 @@ public class GBDeviceApp {
private final UUID uuid;
private final Type type;
private final boolean inCache;
private boolean isOnDevice;
private final boolean configurable;
public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) {
@ -23,6 +24,7 @@ public class GBDeviceApp {
//FIXME: do not assume
this.inCache = false;
this.configurable = false;
this.isOnDevice = false;
}
public GBDeviceApp(JSONObject json, boolean configurable) {
@ -52,10 +54,18 @@ public class GBDeviceApp {
this.configurable = configurable;
}
public void setOnDevice(boolean isOnDevice) {
this.isOnDevice = isOnDevice;
}
public boolean isInCache() {
return inCache;
}
public boolean isOnDevice() {
return isOnDevice;
}
public String getName() {
return name;
}

View File

@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
@ -223,4 +224,24 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
Intent intent = createIntent().setAction(ACTION_ADD_CALENDAREVENT)
.putExtra(EXTRA_CALENDAREVENT_ID, calendarEventSpec.id)
.putExtra(EXTRA_CALENDAREVENT_TYPE, calendarEventSpec.type)
.putExtra(EXTRA_CALENDAREVENT_TIMESTAMP, calendarEventSpec.timestamp)
.putExtra(EXTRA_CALENDAREVENT_DURATION, calendarEventSpec.durationInSeconds)
.putExtra(EXTRA_CALENDAREVENT_TITLE, calendarEventSpec.title)
.putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description);
invokeService(intent);
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
Intent intent = createIntent().setAction(ACTION_DELETE_CALENDAREVENT)
.putExtra(EXTRA_CALENDAREVENT_TYPE, type)
.putExtra(EXTRA_CALENDAREVENT_ID, id);
invokeService(intent);
}
}

View File

@ -0,0 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class CalendarEventSpec {
public static final byte TYPE_UNKNOWN = 0;
public static final byte TYPE_SUNRISE = 1;
public static final byte TYPE_SUNSET = 2;
public byte type;
public long id;
public int timestamp;
public int durationInSeconds;
public String title;
public String description;
}

View File

@ -36,6 +36,8 @@ public interface DeviceService extends EventHandler {
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement";
String ACTION_ADD_CALENDAREVENT = PREFIX + ".action.add_calendarevent";
String ACTION_DELETE_CALENDAREVENT = PREFIX + ".action.delete_calendarevent";
String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
@ -65,6 +67,12 @@ public interface DeviceService extends EventHandler {
String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_TIMESTAMP = "timestamp";
String EXTRA_HEART_RATE_VALUE = "hr_value";
String EXTRA_CALENDAREVENT_ID = "calendarevent_id";
String EXTRA_CALENDAREVENT_TYPE = "calendarevent_type";
String EXTRA_CALENDAREVENT_TIMESTAMP = "calendarevent_timestamp";
String EXTRA_CALENDAREVENT_DURATION = "calendarevent_duration";
String EXTRA_CALENDAREVENT_TITLE = "calendarevent_title";
String EXTRA_CALENDAREVENT_DESCRIPTION = "calendarevent_description";
void start();

View File

@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.NotificationManager;
import android.app.Service;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -24,6 +25,8 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.K9Receiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
@ -32,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -41,10 +45,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETE_CALENDAREVENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
@ -68,6 +74,12 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DESCRIPTION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_ID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TIMESTAMP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS;
@ -105,6 +117,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
private PebbleReceiver mPebbleReceiver = null;
private MusicPlaybackReceiver mMusicPlaybackReceiver = null;
private TimeChangeReceiver mTimeChangeReceiver = null;
private BluetoothConnectReceiver mBlueToothConnectReceiver = null;
private AlarmReceiver mAlarmReceiver = null;
private Random mRandom = new Random();
@ -265,6 +279,23 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.onNotification(notificationSpec);
break;
}
case ACTION_ADD_CALENDAREVENT: {
CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
calendarEventSpec.id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
calendarEventSpec.type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
calendarEventSpec.timestamp = intent.getIntExtra(EXTRA_CALENDAREVENT_TIMESTAMP, -1);
calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1);
calendarEventSpec.title = intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE);
calendarEventSpec.description = intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION);
mDeviceSupport.onAddCalendarEvent(calendarEventSpec);
break;
}
case ACTION_DELETE_CALENDAREVENT: {
long id = intent.getLongExtra(EXTRA_CALENDAREVENT_ID, -1);
byte type = intent.getByteExtra(EXTRA_CALENDAREVENT_TYPE, (byte) -1);
mDeviceSupport.onDeleteCalendarEvent(type, id);
break;
}
case ACTION_REBOOT: {
mDeviceSupport.onReboot();
break;
@ -279,6 +310,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
case ACTION_DISCONNECT: {
mDeviceSupport.dispose();
if (mGBDevice != null && mGBDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
setReceiversEnableState(false);
mGBDevice.setState(GBDevice.State.NOT_CONNECTED);
mGBDevice.sendDeviceUpdateIntent(this);
}
mDeviceSupport = null;
break;
}
@ -457,6 +493,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
filter.addAction("android.intent.action.TIMEZONE_CHANGED");
registerReceiver(mTimeChangeReceiver, filter);
}
if (mBlueToothConnectReceiver == null) {
mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
}
if (mAlarmReceiver == null) {
mAlarmReceiver = new AlarmReceiver();
registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM"));
}
} else {
if (mPhoneCallReceiver != null) {
unregisterReceiver(mPhoneCallReceiver);
@ -482,6 +526,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
unregisterReceiver(mTimeChangeReceiver);
mTimeChangeReceiver = null;
}
if (mBlueToothConnectReceiver != null) {
unregisterReceiver(mBlueToothConnectReceiver);
mBlueToothConnectReceiver = null;
}
if (mAlarmReceiver != null) {
unregisterReceiver(mAlarmReceiver);
mAlarmReceiver = null;
}
}
}
@ -549,4 +601,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
public GBPrefs getGBPrefs() {
return GBApplication.getGBPrefs();
}
public GBDevice getGBDevice() {
return mGBDevice;
}
}

View File

@ -13,6 +13,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -268,4 +269,20 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onEnableRealtimeHeartRateMeasurement(enable);
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
if (checkBusy("add calendar event")) {
return;
}
delegate.onAddCalendarEvent(calendarEventSpec);
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
if (checkBusy("delete calendar event")) {
return;
}
delegate.onDeleteCalendarEvent(type, id);
}
}

View File

@ -147,7 +147,6 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
}
private void gattServicesDiscovered(List<BluetoothGattService> discoveredGattServices) {
if (discoveredGattServices == null) {
return;
}
@ -180,7 +179,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
@Override
public void onServicesDiscovered(BluetoothGatt gatt) {
gattServicesDiscovered(getQueue().getSupportedGattServices());
gattServicesDiscovered(gatt.getServices());
initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue());
}

View File

@ -156,11 +156,9 @@ public final class BtLEQueue {
LOG.info("Attempting to connect to " + mGbDevice.getName());
mBluetoothAdapter.cancelDiscovery();
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
// boolean result;
synchronized (mGattMonitor) {
// connectGatt with true doesn't really work ;( too often connection problems
mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
// result = mBluetoothGatt.connect();
}
boolean result = mBluetoothGatt != null;
if (result) {
@ -223,7 +221,11 @@ public final class BtLEQueue {
private boolean maybeReconnect() {
if (mAutoReconnect && mBluetoothGatt != null) {
LOG.info("Enabling automatic ble reconnect...");
return mBluetoothGatt.connect();
boolean result = mBluetoothGatt.connect();
if (result) {
setDeviceConnectionState(State.WAITING_FOR_RECONNECT);
}
return result;
}
return false;
}
@ -308,6 +310,12 @@ public final class BtLEQueue {
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
LOG.debug("connection state change, newState: " + newState + getStatusString(status));
synchronized (mGattMonitor) {
if (mBluetoothGatt == null) {
mBluetoothGatt = gatt;
}
}
if (!checkCorrectGattInstance(gatt, "connection state event")) {
return;
}

View File

@ -83,7 +83,7 @@ public class DeviceInfo extends AbstractInfo {
}
public boolean supportsHeartrate() {
return isMili1S() || (test1AHRMode && isMili1A());
return isMiliPro() || isMili1S() || (test1AHRMode && isMili1A());
}
@Override
@ -116,6 +116,10 @@ public class DeviceInfo extends AbstractInfo {
return hwVersion == 6;
}
public boolean isMiliPro() {
return hwVersion == 8 || (feature == 8 && appearance == 0);
}
public String getHwVersion() {
if (isMili1()) {
return MiBandConst.MI_1;
@ -129,6 +133,9 @@ public class DeviceInfo extends AbstractInfo {
if (isAmazFit()) {
return MiBandConst.MI_AMAZFIT;
}
if (isMiliPro()) {
return MiBandConst.MI_PRO;
}
return "?";
}
}

View File

@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
@ -360,10 +361,18 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
LOG.info("Attempting to set wear location...");
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
if (characteristic != null) {
transaction.add(new ConditionalWriteAction(characteristic) {
@Override
protected byte[] checkCondition() {
if (getDeviceInfo() != null && getDeviceInfo().isAmazFit()) {
return null;
}
int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
transaction.write(characteristic, new byte[]{
return new byte[]{
MiBandService.COMMAND_SET_WEAR_LOCATION,
(byte) location
};
}
});
} else {
LOG.info("Unable to set Wear Location");
@ -382,6 +391,16 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
// not supported
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
// not supported
}
/**
* Part of device initialization process. Do not call manually.
*
@ -834,6 +853,9 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private void handleHeartrate(byte[] value) {
if (value.length == 2 && value[0] == 6) {
int hrValue = (value[1] & 0xff);
if (LOG.isDebugEnabled()) {
LOG.debug("heart rate: " + hrValue);
}
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());

View File

@ -23,6 +23,10 @@ public class AppMessageHandler {
mPebbleProtocol = pebbleProtocol;
}
public boolean isEnabled() {
return true;
}
public UUID getUUID() {
return mUUID;
}

View File

@ -18,11 +18,13 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class AppMessageHandlerMisfit extends AppMessageHandler {
@ -42,6 +44,13 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
super(uuid, pebbleProtocol);
}
@Override
public boolean isEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MISFIT);
}
@Override
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
for (Pair<Integer, Object> pair : pairs) {

View File

@ -11,7 +11,6 @@ import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
@ -21,7 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MorpheuzSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class AppMessageHandlerMorpheuz extends AppMessageHandler {
@ -60,6 +59,13 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler {
return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs);
}
@Override
public boolean isEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MORPHEUZ);
}
@Override
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
int ctrl_message = 0;

View File

@ -0,0 +1,103 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class);
public DatalogSessionHealthOverlayData(byte id, UUID uuid, int tag, byte item_type, short item_size) {
super(id, uuid, tag, item_type, item_size);
taginfo = "(health - overlay data " + tag + " )";
}
@Override
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
if (!isPebbleHealthEnabled()) {
return false;
}
int initialPosition = datalogMessage.position();
int beginOfRecordPosition;
short recordVersion; //probably
short recordType; //probably: 1=sleep, 2=deep sleep, 5=??run??ignored for now
if (0 != (length % itemSize))
return false;//malformed message?
int recordCount = length / itemSize;
OverlayRecord[] overlayRecords = new OverlayRecord[recordCount];
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
recordVersion = datalogMessage.getShort();
if ((recordVersion != 1) && (recordVersion != 3))
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
datalogMessage.getShort();//throwaway, unknown
recordType = datalogMessage.getShort();
overlayRecords[recordIdx] = new OverlayRecord(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt());
}
return store(overlayRecords);//NACK if we cannot store the data yet, the watch will send the overlay records again.
}
private boolean store(OverlayRecord[] overlayRecords) {
DBHandler dbHandler = null;
SampleProvider sampleProvider = new HealthSampleProvider();
try {
dbHandler = GBApplication.acquireDB();
int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider);
for (OverlayRecord overlayRecord : overlayRecords) {
if (latestTimestamp < (overlayRecord.timestampStart + overlayRecord.durationSeconds))
return false;
switch (overlayRecord.type) {
case 1:
dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
break;
case 2:
dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider);
break;
default:
//TODO: other values refer to unknown activity types.
}
}
} catch (Exception ex) {
LOG.debug(ex.getMessage());
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
return true;
}
private class OverlayRecord {
int type; //1=sleep, 2=deep sleep
int offsetUTC; //probably
int timestampStart;
int durationSeconds;
public OverlayRecord(int type, int offsetUTC, int timestampStart, int durationSeconds) {
this.type = type;
this.offsetUTC = offsetUTC;
this.timestampStart = timestampStart;
this.durationSeconds = durationSeconds;
}
}
}

View File

@ -1,7 +1,5 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
class DatalogSessionHealthSleep extends DatalogSession {
class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class);
@ -27,65 +25,11 @@ class DatalogSessionHealthSleep extends DatalogSession {
@Override
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
switch (this.tag) {
case 83:
return handleMessage83(datalogMessage, length);
case 84:
return handleMessage84(datalogMessage, length);
default:
if (!isPebbleHealthEnabled()) {
return false;
}
}
private boolean handleMessage84(ByteBuffer datalogMessage, int length) {
int initialPosition = datalogMessage.position();
int beginOfRecordPosition;
short recordVersion; //probably
short recordType; //probably: 1=sleep, 2=deep sleep
if (0 != (length % itemSize))
return false;//malformed message?
int recordCount = length / itemSize;
SleepRecord84[] sleepRecords = new SleepRecord84[recordCount];
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
recordVersion = datalogMessage.getShort();
if (recordVersion != 1)
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
datalogMessage.getShort();//throwaway, unknown
recordType = datalogMessage.getShort();
sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt());
}
return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
}
private boolean store84(SleepRecord84[] sleepRecords) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
for (SleepRecord84 sleepRecord : sleepRecords) {
if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds))
return false;
if (sleepRecord.type == 2) {
sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP));
} else {
sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
}
}
} catch (Exception ex) {
LOG.debug(ex.getMessage());
}
return true;
}
private boolean handleMessage83(ByteBuffer datalogMessage, int length) {
int initialPosition = datalogMessage.position();
int beginOfRecordPosition;
short recordVersion; //probably
@ -94,7 +38,7 @@ class DatalogSessionHealthSleep extends DatalogSession {
return false;//malformed message?
int recordCount = length / itemSize;
SleepRecord83[] sleepRecords = new SleepRecord83[recordCount];
SleepRecord[] sleepRecords = new SleepRecord[recordCount];
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
@ -103,21 +47,20 @@ class DatalogSessionHealthSleep extends DatalogSession {
if (recordVersion != 1)
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(),
sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(),
datalogMessage.getInt(),
datalogMessage.getInt(),
datalogMessage.getInt());
}
return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
}
private boolean store83(SleepRecord83[] sleepRecords) {
private boolean store(SleepRecord[] sleepRecords) {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO);
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
for (SleepRecord83 sleepRecord : sleepRecords) {
for (SleepRecord sleepRecord : sleepRecords) {
if (latestTimestamp < sleepRecord.bedTimeEnd)
return false;
sampleProvider.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
@ -128,13 +71,13 @@ class DatalogSessionHealthSleep extends DatalogSession {
return true;
}
private class SleepRecord83 {
private class SleepRecord {
int offsetUTC; //probably
int bedTimeStart;
int bedTimeEnd;
int deepSleepSeconds;
public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
this.offsetUTC = offsetUTC;
this.bedTimeStart = bedTimeStart;
this.bedTimeEnd = bedTimeEnd;
@ -142,17 +85,4 @@ class DatalogSessionHealthSleep extends DatalogSession {
}
}
private class SleepRecord84 {
int type; //1=sleep, 2=deep sleep
int offsetUTC; //probably
int timestampStart;
int durationSeconds;
public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) {
this.type = type;
this.offsetUTC = offsetUTC;
this.timestampStart = timestampStart;
this.durationSeconds = durationSeconds;
}
}
}

View File

@ -20,7 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DatalogSessionHealthSteps extends DatalogSession {
public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class);
private final GBDevice device;
@ -35,6 +35,10 @@ public class DatalogSessionHealthSteps extends DatalogSession {
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
if (!isPebbleHealthEnabled()) {
return false;
}
int timestamp;
byte recordLength, recordNum;
short recordVersion; //probably

View File

@ -0,0 +1,20 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
abstract class DatalogSessionPebbleHealth extends DatalogSession {
DatalogSessionPebbleHealth(byte id, UUID uuid, int tag, byte itemType, short itemSize) {
super(id, uuid, tag, itemType, itemSize);
}
protected boolean isPebbleHealthEnabled() {
Prefs prefs = GBApplication.getPrefs();
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
return (activityTracker == SampleProvider.PROVIDER_PEBBLE_HEALTH);
}
}

View File

@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -48,9 +47,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class PebbleIoThread extends GBDeviceIoThread {
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
private static final UUID PEBBLE_UUID_RECONNECT = UUID.fromString("00000000-deca-fade-deca-deafdecacafe");
private static final UUID PEBBLE_UUID_RECONNECT3X = UUID.fromString("a924496e-cc7c-4dff-8a9f-9a76cc2e9d50");
public static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED";
public static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED";
public static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK";
@ -71,7 +67,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
private boolean mIsTCP = false;
private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null;
private BluetoothServerSocket mBtServerSocket = null;
private Socket mTCPSocket = null; // for emulator
private InputStream mInStream = null;
private OutputStream mOutStream = null;
@ -166,6 +161,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
@Override
protected boolean connect(String btDeviceAddress) {
GBDevice.State originalState = gbDevice.getState();
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
try {
// contains only one ":"? then it is addr:port
int firstColon = btDeviceAddress.indexOf(":");
@ -193,6 +190,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
} catch (IOException e) {
e.printStackTrace();
gbDevice.setState(originalState);
gbDevice.sendDeviceUpdateIntent(getContext());
mInStream = null;
mOutStream = null;
mBtSocket = null;
@ -202,12 +201,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
mPebbleProtocol.setForceProtocol(prefs.getBoolean("pebble_force_protocol", false));
mIsConnected = true;
if (originalState == GBDevice.State.WAITING_FOR_RECONNECT) {
gbDevice.setState(GBDevice.State.INITIALIZED);
} else {
gbDevice.setState(GBDevice.State.CONNECTED);
write(mPebbleProtocol.encodeFirmwareVersionReq());
}
gbDevice.setState(GBDevice.State.CONNECTED);
gbDevice.sendDeviceUpdateIntent(getContext());
return true;
@ -215,15 +210,18 @@ public class PebbleIoThread extends GBDeviceIoThread {
@Override
public void run() {
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
mIsConnected = connect(gbDevice.getAddress());
enablePebbleKitReceiver(mIsConnected);
mQuit = !mIsConnected; // quit if not connected
if (!mIsConnected) {
if (GBApplication.getGBPrefs().getAutoReconnect()) {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
gbDevice.sendDeviceUpdateIntent(getContext());
}
return;
}
byte[] buffer = new byte[8192];
enablePebbleKitReceiver(true);
mQuit = false;
while (!mQuit) {
try {
if (mIsInstalling) {
@ -365,9 +363,10 @@ public class PebbleIoThread extends GBDeviceIoThread {
LOG.info(e.getMessage());
mIsConnected = false;
int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10);
if (GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) {
gbDevice.setState(GBDevice.State.CONNECTING);
if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
gbDevice.sendDeviceUpdateIntent(getContext());
int delaySeconds = 1;
while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) {
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
@ -383,33 +382,10 @@ public class PebbleIoThread extends GBDeviceIoThread {
}
}
}
if (!mIsConnected && !mQuit) {
try {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
gbDevice.sendDeviceUpdateIntent(getContext());
UUID reconnectUUID = mPebbleProtocol.isFw3x ? PEBBLE_UUID_RECONNECT3X : PEBBLE_UUID_RECONNECT;
mBtServerSocket = mBtAdapter.listenUsingRfcommWithServiceRecord("PebbleReconnectListener", reconnectUUID);
mBtSocket = mBtServerSocket.accept();
LOG.info("incoming connection on reconnect uuid (" + reconnectUUID + "), will connect actively");
mBtSocket.close();
mIsConnected = connect(gbDevice.getAddress());
} catch (IOException ex) {
ex.printStackTrace();
LOG.info("error while reconnecting");
} finally {
try {
if (mBtServerSocket != null) {
mBtServerSocket.close();
mBtServerSocket = null;
}
} catch (IOException ignore) {
}
}
}
if (!mIsConnected) {
mBtSocket = null;
LOG.info("Bluetooth socket closed, will quit IO Thread");
mQuit = true;
break;
}
}
}
@ -421,10 +397,16 @@ public class PebbleIoThread extends GBDeviceIoThread {
} catch (IOException e) {
e.printStackTrace();
}
}
enablePebbleKitReceiver(false);
mBtSocket = null;
}
enablePebbleKitReceiver(false);
if (mQuit) {
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
} else {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
}
gbDevice.sendDeviceUpdateIntent(getContext());
}
@ -700,13 +682,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
e.printStackTrace();
}
}
if (mBtServerSocket != null) {
try {
mBtServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (mTCPSocket != null) {
try {
mTCPSocket.close();

View File

@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
@ -241,6 +242,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte LENGTH_UUID = 16;
static final long GB_UUID_MASK = 0x4767744272646700L;
// base is -5
private static final String[] hwRevisions = {
// Emulator
@ -452,7 +455,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (isFw3x) {
// 3.x notification
//return encodeTimelinePin(id, (int) ((ts + 600) & 0xffffffffL), (short) 90, PebbleIconID.TIMELINE_CALENDAR, title); // really, this is just for testing
return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.cannedReplies);
} else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) {
// 2.x notification
@ -465,6 +467,29 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
}
@Override
public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong();
int iconId;
switch (calendarEventSpec.type) {
case CalendarEventSpec.TYPE_SUNRISE:
iconId = PebbleIconID.SUNRISE;
break;
case CalendarEventSpec.TYPE_SUNSET:
iconId = PebbleIconID.SUNSET;
break;
default:
iconId = PebbleIconID.TIMELINE_CALENDAR;
}
return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) calendarEventSpec.durationInSeconds, iconId, calendarEventSpec.title, calendarEventSpec.description);
}
@Override
public byte[] encodeDeleteCalendarEvent(byte type, long id) {
return encodeBlobdb(new UUID(GB_UUID_MASK | type, id), BLOBDB_DELETE, BLOBDB_PIN, null);
}
@Override
public byte[] encodeSetTime() {
long ts = System.currentTimeMillis();
@ -743,11 +768,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array();
}
private byte[] encodeTimelinePin(int id, int timestamp, short duration, int icon_id, String title, String subtitle) {
private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, String title, String subtitle) {
final short TIMELINE_PIN_LENGTH = 46;
icon_id |= 0x80000000;
UUID uuid = new UUID(mRandom.nextLong(), ((long) mRandom.nextInt() << 32) | id);
byte attributes_count = 2;
byte actions_count = 0;
@ -1235,10 +1259,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put(PHONEVERSION_APPVERSION_MAGIC);
buf.put((byte) 3); // major
buf.put((byte) 8); // minor
buf.put((byte) 1); // patch
buf.put((byte) 12); // minor
buf.put((byte) 0); // patch
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putLong(0x00000000000000af); //flags
buf.putLong(0x00000000000001af); //flags
return buf.array();
}
@ -1803,7 +1827,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
LOG.info(ENDPOINT_NAME + ": (cmd:" + command + ")" + uuid);
break;
}
return null;
return new GBDeviceEvent[]{null};
}
private GBDeviceEvent decodeBlobDb(ByteBuffer buf) {
@ -1881,9 +1905,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
if (!mDatalogSessions.containsKey(id)) {
if (uuid.equals(UUID_ZERO) && log_tag == 81) {
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice()));
} else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) {
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice())));
} else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size));
} else if (uuid.equals(UUID_ZERO) && log_tag == 84) {
mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size));
} else {
mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size));
}
@ -2091,8 +2117,12 @@ public class PebbleProtocol extends GBDeviceProtocol {
AppMessageHandler handler = mAppMessageHandlers.get(uuid);
if (handler != null) {
if (handler.isEnabled()) {
ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
devEvts = handler.handleMessage(dict);
} else {
devEvts = new GBDeviceEvent[]{null};
}
} else {
try {
devEvts = decodeDictToJSONAppMessage(uuid, buf);

View File

@ -12,6 +12,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -78,6 +79,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
private boolean reconnect() {
if (!isConnected() && useAutoConnect()) {
if (getDevice().getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
gbDeviceIOThread.quit();
gbDeviceIOThread.interrupt();
gbDeviceIOThread = null;
if (!connect()) {
@ -118,4 +120,18 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
//nothing to do ATM
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
if (reconnect()) {
super.onAddCalendarEvent(calendarEventSpec);
}
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
if (reconnect()) {
super.onDeleteCalendarEvent(type, id);
}
}
}

View File

@ -8,6 +8,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -187,4 +188,16 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable);
sendToDevice(bytes);
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
byte[] bytes = gbDeviceProtocol.encodeAddCalendarEvent(calendarEventSpec);
sendToDevice(bytes);
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
byte[] bytes = gbDeviceProtocol.encodeDeleteCalendarEvent(type, id);
sendToDevice(bytes);
}
}

View File

@ -4,6 +4,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
public abstract class GBDeviceProtocol {
@ -72,6 +73,14 @@ public abstract class GBDeviceProtocol {
public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; }
public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
return null;
}
public byte[] encodeDeleteCalendarEvent(byte type, long id) {
return null;
}
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
return null;
}

View File

@ -182,4 +182,31 @@ public class FileUtils {
}
return out.toByteArray();
}
public static boolean deleteRecursively(File dir) {
if (!dir.exists()) {
return true;
}
if (dir.isFile()) {
return dir.delete();
}
for (File sub : dir.listFiles()) {
if (!deleteRecursively(sub)) {
return false;
}
}
return dir.delete();
}
public static File createTempDir(String prefix) throws IOException {
File parent = new File(System.getProperty("java.io.tmpdir", "/tmp"));
for (int i = 1; i < 100; i++) {
String name = prefix + (int) (Math.random() * 100000);
File dir = new File(parent, name);
if (dir.mkdirs()) {
return dir;
}
}
throw new IOException("Cannot create temporary directory in " + parent);
}
}

View File

@ -6,6 +6,9 @@
<item
android:id="@+id/appmanager_app_delete"
android:title="@string/appmananger_app_delete"/>
<item
android:id="@+id/appmanager_app_delete_cache"
android:title="@string/appmananger_app_delete_cache"/>
<item
android:id="@+id/appmanager_health_activate"
android:title="@string/appmanager_health_activate"/>

View File

@ -14,6 +14,7 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">App Manager</string>
<string name="appmananger_app_delete">Löschen</string>
<string name="appmananger_app_delete_cache">Löschen und aus dem Zwischenspeicher entfernen</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Sperre für Benachrichtigungen</string>
<!--Strings related to FwAppInstaller-->
@ -27,6 +28,7 @@
<string name="title_activity_settings">Einstellungen</string>
<string name="pref_header_general">Allgemeine Einstellungen</string>
<string name="pref_title_general_autoconnectonbluetooth">Verbinde, wenn Bluetooth eingeschaltet wird</string>
<string name="pref_title_general_autocreonnect">Verbindungen automatisch wiederherstellen</string>
<string name="pref_title_audo_player">Bevorzugter Audioplayer</string>
<string name="pref_default">Standard</string>
<string name="pref_header_datetime">Datum und Zeit</string>
@ -44,6 +46,8 @@
<string name="pref_summary_notifications_pebblemsg">Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden.</string>
<string name="pref_title_notifications_generic">Andere Benachrichtigungen</string>
<string name="pref_title_whenscreenon">… auch wenn der Bildschirm an ist</string>
<string name="pref_title_notification_filter">Bitte nicht stören</string>
<string name="pref_summary_notification_filter">Stoppe unerwünschte Nachrichten, wenn im \"Nicht Stören\"-Modus</string>
<string name="always">immer</string>
<string name="when_screen_off">wenn der Bildschirm aus ist</string>
<string name="never">niemals</string>
@ -56,6 +60,12 @@
<string name="pref_title_pebble_activitytracker">Bevorzugter Aktivitätstracker</string>
<string name="pref_title_enable_pebblekit">Erlaube Zugriff von anderen Android Apps</string>
<string name="pref_summary_enable_pebblekit">Experimentelle Unterstützung für Android Apps, die PebbleKit benutzen</string>
<string name="pref_title_sunrise_sunset">Sonnenauf- und -untergang </string>
<string name="pref_summary_sunrise_sunset">Sende Sonnenauf- und -untergangszeiten abhänging vom Standort auf die Pebble Timeline</string>
<string name="pref_header_location">Standort</string>
<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_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>
@ -202,8 +212,9 @@
<string name="pref_title_keep_data_on_device">Aktivitätsdaten auf dem Gerät lassen</string>
<string name="miband_fwinstaller_incompatible_version">Inkompatible Firmware</string>
<string name="fwinstaller_firmware_not_compatible_to_device">Diese Firmware ist nicht mit dem Gerät kompatibel</string>
<string name="miband_prefs_reserve_alarm_calendar">Wecker für zukünftige Ereignisse vormerken</string>
<string name="miband_prefs_hr_sleep_detection">Verwende den Herzfrequenzsensor um die Schlaferkennung zu verbessern</string>
<string name="waiting_for_reconnect">warte auf eingehende Verbindung</string>
<string name="waiting_for_reconnect">warte auf Verbindung</string>
<string name="appmananger_app_reinstall">Erneut installieren</string>
<string name="activity_prefs_about_you">Über Dich</string>
<string name="activity_prefs_year_birth">Geburtsjahr</string>
@ -214,6 +225,7 @@
<string name="appmanager_health_deactivate">deaktivieren</string>
<string name="authenticating">Authentifiziere</string>
<string name="authentication_required">Authentifizierung erforderlich</string>
<string name="app_configure">Konfigurieren</string>
<string name="appwidget_text">Zzz</string>
<string name="add_widget">Widget hinzufügen</string>
<string name="activity_prefs_sleep_duration">Gewünschte Schlafdauer in Stunden</string>
@ -226,5 +238,4 @@
<string name="updatefirmwareoperation_firmware_not_sent">Firmware wurde nicht gesendet</string>
<string name="charts_legend_heartrate">Herzfrequenz</string>
<string name="live_activity_heart_rate">Herzfrequenz</string>
<string name="pref_title_general_autocreonnect">Verbindungen automatisch wiederherstellen</string>
</resources>

View File

@ -14,6 +14,7 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">Gestione app</string>
<string name="appmananger_app_delete">Cancella</string>
<string name="appmananger_app_delete_cache">Cancella e rimuovi dalla cache</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">Blocco notifiche</string>
<!--Strings related to FwAppInstaller-->
@ -27,11 +28,15 @@
<string name="title_activity_settings">Impostazioni</string>
<string name="pref_header_general">Impostazioni globali</string>
<string name="pref_title_general_autoconnectonbluetooth">Collegati al dispositivo quando il bluetooth viene acceso</string>
<string name="pref_title_general_autocreonnect">Riconessione automatica</string>
<string name="pref_title_audo_player">Applicazione musicale preferita</string>
<string name="pref_default">Default</string>
<string name="pref_header_datetime">Data e ora</string>
<string name="pref_title_datetime_syctimeonconnect">Sincronizza l\'ora</string>
<string name="pref_summary_datetime_syctimeonconnect">Sincronizza l\'orario al collegamento oppure quando viene cambiata l\'ora / il fuso orario in android.</string>
<string name="pref_title_theme">Tema</string>
<string name="pref_theme_light">Chiaro</string>
<string name="pref_theme_dark">Scuro</string>
<string name="pref_header_notifications">Notifiche</string>
<string name="pref_title_notifications_repetitions">Ripetizioni</string>
<string name="pref_title_notifications_call">Chiamate telefoniche</string>
@ -41,6 +46,8 @@
<string name="pref_summary_notifications_pebblemsg">Supporto per applicazioni che inviano le notifiche a Pebble usando Intents. Può essere usato per Conversations.</string>
<string name="pref_title_notifications_generic">Notifiche generiche</string>
<string name="pref_title_whenscreenon">… anche se lo schermo è acceso</string>
<string name="pref_title_notification_filter">Non disturbare</string>
<string name="pref_summary_notification_filter">Non inviare notifiche nei periodi configurati come \"non disturbare\"</string>
<string name="always">sempre</string>
<string name="when_screen_off">se lo schermo è spento</string>
<string name="never">mai</string>
@ -53,6 +60,12 @@
<string name="pref_title_pebble_activitytracker">Tracker delle attività preferito</string>
<string name="pref_title_enable_pebblekit">Consenti accesso ad altre applicazioni</string>
<string name="pref_summary_enable_pebblekit">Attiva l\'accesso sperimentale ad applicazioni Android che usano PebbleKit</string>
<string name="pref_title_sunrise_sunset">Alba e tramonto</string>
<string name="pref_summary_sunrise_sunset">Mostra gli orari calcolati per l\'alba e il tramonto sulla timeline</string>
<string name="pref_header_location">Posizione</string>
<string name="pref_title_location_aquire">Acquisisci posizione</string>
<string name="pref_title_location_latitude">Latitudin</string>
<string name="pref_title_location_longitude">Longitudin</string>
<string name="pref_title_pebble_forceprotocol">Forza protocollo delle notifiche</string>
<string name="pref_summary_pebble_forceprotocol">Questa opzione forza l\'utilizzo della versione più recente delle notifiche in dipendenza del firmware del tuo dispositivo. ABILITALO SOLO SE SAI COSA STAI FACENDO!</string>
<string name="pref_title_pebble_forceuntested">Abilita funzionalità non testate</string>
@ -70,6 +83,9 @@
<string name="this_is_a_test_notification_from_gadgetbridge">Notifica di prova creata da Gadgetbridge</string>
<string name="bluetooth_is_not_supported_">Bluetooth non supportato.</string>
<string name="bluetooth_is_disabled_">Bluetooth disabilitato.</string>
<string name="tap_connected_device_for_app_mananger">tocca il dispositivo connesso per gestire le App</string>
<string name="tap_connected_device_for_activity">tocca il dispositivo connesso per visualizzare l\'attività</string>
<string name="tap_a_device_to_connect">tocca il dispositivo a cui connettersi</string>
<string name="cannot_connect_bt_address_invalid_">Impossibile connettersi. Indirizzo BT non valido?</string>
<string name="gadgetbridge_running">Gadgetbridge in esecuzione</string>
<string name="installing_binary_d_d">installazione del binario %1$d/%2$d</string>
@ -100,11 +116,13 @@
<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>
<string name="pref_header_vibration_count">Numero vibrazioni</string>
<string name="title_activity_sleepmonitor">Monitoraggio del sonno</string>
<string name="pref_write_logfiles">Salva il log su file</string>
<string name="initializing">inizializzazione in corso</string>
<string name="busy_task_fetch_activity_data">Recupero dati attività</string>
<string name="sleep_activity_date_range">Da %1$s a %2$s</string>
@ -178,6 +196,8 @@
<string name="pref_title_dont_ack_transfer">Non confermare il trasferimento dati</string>
<string name="pref_summary_dont_ack_transfers">Se il trasferimento non viene confermato, i dati rimangono memorizzati sulla Mi Band. Utile se GB è usato insieme ad altre app.</string>
<string name="pref_summary_keep_data_on_device">Conserva i dati delle attività sulla Mi Band anche dopo averli sincronizzati. Utile se GB è usato insieme ad altre app.</string>
<string name="pref_title_low_latency_fw_update">Utilizza la modalità a bassa latenza per gli aggiornamenti del firmware</string>
<string name="pref_summary_low_latency_fw_update">Può essere utile quando l\'aggiornamento del firmware fallisce</string>
<string name="live_activity_steps_history">Storico dei passi</string>
<string name="live_activity_current_steps_per_minute">Passi/minuto</string>
<string name="live_activity_total_steps">Passi totali</string>
@ -193,6 +213,7 @@
<string name="miband_fwinstaller_incompatible_version">Firmware non compatibile</string>
<string name="fwinstaller_firmware_not_compatible_to_device">Questo firmware non è compatibile con il dispositivo</string>
<string name="miband_prefs_reserve_alarm_calendar">Sveglie da riservare per i prossimi eventi del calendario</string>
<string name="miband_prefs_hr_sleep_detection">Utilizza il sensore del battito cardiaco per migliorare il riconoscimento del sonno</string>
<string name="waiting_for_reconnect">in attesa di riconessione</string>
<string name="appmananger_app_reinstall">Re-installazion</string>
<string name="activity_prefs_about_you">Informazioni sull\'utilizzatore</string>
@ -211,4 +232,11 @@
<string name="appwidget_alarms_set">Impostata sveglia per %1$02d:%2$02d</string>
<string name="device_hw">HW: %1$s</string>
<string name="device_fw">FW: %1$s</string>
<string name="error_creating_directory_for_logfiles">Errore durante la creazione della directory per i file di log: %1$s</string>
<string name="DEVINFO_HR_VER">HR:</string>
<string name="updatefirmwareoperation_update_in_progress">Aggiornamento firmware in corso</string>
<string name="updatefirmwareoperation_firmware_not_sent">Firmware non inviato</string>
<string name="charts_legend_heartrate">Battito cardiaco</string>
<string name="live_activity_heart_rate">Battito cardiaco</string>
<string name="miband_prefs_device_time_offset_hours">Offset orologio del dispositivo in ore (per l\'identificazione del sonno dei lavoratori a turni)</string>
</resources>

View File

@ -14,6 +14,7 @@
<!--Strings related to AppManager-->
<string name="title_activity_appmanager">アプリ管理画面</string>
<string name="appmananger_app_delete">削除</string>
<string name="appmananger_app_delete_cache">キャッシュから削除</string>
<!--Strings related to AppBlacklist-->
<string name="title_activity_appblacklist">ステータス通知ブラックリスト</string>
<!--Strings related to FwAppInstaller-->
@ -27,6 +28,7 @@
<string name="title_activity_settings">設定</string>
<string name="pref_header_general">一般設定</string>
<string name="pref_title_general_autoconnectonbluetooth">Bluetoothがオンになったときにデバイスに接続</string>
<string name="pref_title_general_autocreonnect">自動的に再接続</string>
<string name="pref_title_audo_player">お好みのオーディオプレイヤー</string>
<string name="pref_default">デフォルト</string>
<string name="pref_header_datetime">日付と時刻</string>
@ -44,6 +46,8 @@
<string name="pref_summary_notifications_pebblemsg">インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。</string>
<string name="pref_title_notifications_generic">一般ステータス通知対応</string>
<string name="pref_title_whenscreenon">… スクリーンがオンのときにも</string>
<string name="pref_title_notification_filter">サイレント</string>
<string name="pref_summary_notification_filter">サイレントモードに基づいて、送信される不要な通知を停止します。</string>
<string name="always">いつも</string>
<string name="when_screen_off">スクリーンがオフのとき</string>
<string name="never">なし</string>
@ -56,6 +60,12 @@
<string name="pref_title_pebble_activitytracker">お好みのアクティビティ トラッカー</string>
<string name="pref_title_enable_pebblekit">第三者のアンドロイドアップにアクセス権利を与える</string>
<string name="pref_summary_enable_pebblekit">PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします</string>
<string name="pref_title_sunrise_sunset">日の出と日の入り</string>
<string name="pref_summary_sunrise_sunset">場所に基づいて、Pebble のタイムラインに日の出・日の入りの時間を送ります</string>
<string name="pref_header_location">場所</string>
<string name="pref_title_location_aquire">場所の取得</string>
<string name="pref_title_location_latitude">緯度</string>
<string name="pref_title_location_longitude">経度</string>
<string name="pref_title_pebble_forceprotocol">通知プロトコルを強制する</string>
<string name="pref_summary_pebble_forceprotocol">このオプションを指定すると、ファームウェアのバージョンに応じて強制的に最新の通知プロトコルを使用します。何をしているかわかっている場合のみ有効にしてください!</string>
<string name="pref_title_pebble_forceuntested">未テストの機能を有効にする</string>
@ -228,5 +238,4 @@
<string name="updatefirmwareoperation_firmware_not_sent">ファームウェアを送信しませんでした</string>
<string name="charts_legend_heartrate">心拍数</string>
<string name="live_activity_heart_rate">心拍数</string>
<string name="pref_title_general_autocreonnect">自動的に再接続</string>
</resources>

View File

@ -27,6 +27,7 @@
<string name="title_activity_settings">설정</string>
<string name="pref_header_general">일반 설정</string>
<string name="pref_title_general_autoconnectonbluetooth">블루투스가 켜지면 기기에 접속하기</string>
<string name="pref_title_general_autocreonnect">자동으로 재연결</string>
<string name="pref_title_audo_player">선호하는 오디오 플레이어</string>
<string name="pref_default">기본값</string>
<string name="pref_header_datetime">날짜와 시간</string>
@ -224,5 +225,4 @@
<string name="updatefirmwareoperation_firmware_not_sent">펌웨어가 전송되지 않음</string>
<string name="charts_legend_heartrate">심박수</string>
<string name="live_activity_heart_rate">심박수</string>
<string name="pref_title_general_autocreonnect">자동으로 재연결</string>
</resources>

View File

@ -11,6 +11,36 @@
<string name="pref_theme_value_light" translatable="false">light</string>
<string name="pref_theme_value_dark" translatable="false">dark</string>
<string-array name="pref_language_options">
<item name="default">System Default</item>
<item name="de">Deutsch</item>
<item name="en">English</item>
<item name="es">Español</item>
<item name="fr">Français</item>
<item name="pl">Polski</item>
<item name="ru">Русский</item>
<item name="vi">Tiếng Việt</item>
<item name="tr">Türkçe</item>
<item name="uk">Українська</item>
<item name="ko">한국어</item>
<item name="ja">日本語</item>
</string-array>
<string-array name="pref_language_values">
<item>default</item>
<item>de</item>
<item>en</item>
<item>es</item>
<item>fr</item>
<item>pl</item>
<item>ru</item>
<item>vi</item>
<item>tr</item>
<item>uk</item>
<item>ko</item>
<item>ja</item>
</string-array>
<string-array name="notification_mode">
<item>@string/always</item>
<item>@string/when_screen_off</item>

View File

@ -17,6 +17,7 @@
<!-- Strings related to AppManager -->
<string name="title_activity_appmanager">App Manager</string>
<string name="appmananger_app_delete">Delete</string>
<string name="appmananger_app_delete_cache">Delete and remove from cache</string>
<!-- Strings related to AppBlacklist -->
<string name="title_activity_appblacklist">Notification Blacklist</string>
@ -34,15 +35,20 @@
<string name="pref_header_general">General Settings</string>
<string name="pref_title_general_autoconnectonbluetooth">Connect to device when Bluetooth turned on</string>
<string name="pref_title_general_autocreonnect">Reconnect automatically</string>
<string name="pref_title_audo_player">Preferred Audioplayer</string>
<string name="pref_default">Default</string>
<string name="pref_header_datetime">Date and Time</string>
<string name="pref_title_datetime_syctimeonconnect">Sync time</string>
<string name="pref_summary_datetime_syctimeonconnect">Sync time to device when connecting and when time or timezone changes on Android</string>
<string name="pref_title_theme">Theme</string>
<string name="pref_theme_light">Light</string>
<string name="pref_theme_dark">Dark</string>
<string name="pref_title_language">Language</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>
@ -71,11 +77,21 @@
<string name="pref_title_pebble_activitytracker">Preferred Activitytracker</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>
<string name="pref_title_sunrise_sunset">Sunrise and Sunset</string>
<string name="pref_summary_sunrise_sunset">Send sunrise and sunset times based on the location to the pebble timeline</string>
<string name="pref_header_location">Location</string>
<string name="pref_title_location_aquire">Acquire Location</string>
<string name="pref_title_location_latitude">Latitude</string>
<string name="pref_title_location_longitude">Longitude</string>
<string name="pref_title_pebble_forceprotocol">Force Notification Protocol</string>
<string name="pref_summary_pebble_forceprotocol">This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="pref_title_pebble_forceuntested">Enable untested features</string>
<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_reconnect_attempts">Reconnection Attempts</string>
<string name="not_connected">not connected</string>
<string name="connecting">connecting</string>
<string name="connected">connected</string>
@ -223,6 +239,7 @@
<string name="fwinstaller_firmware_not_compatible_to_device">This firmware is not compatible with the device</string>
<string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
<string name="miband_prefs_hr_sleep_detection">Use Heartrate Sensor to improve sleep detection</string>
<string name="miband_prefs_device_time_offset_hours">Device time offset in hours (for detecting sleep of shift workers)</string>
<string name="waiting_for_reconnect">waiting for reconnect</string>
<string name="appmananger_app_reinstall">Reinstall</string>
@ -250,6 +267,4 @@
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
<string name="charts_legend_heartrate">Heart Rate</string>
<string name="live_activity_heart_rate">Heart Rate</string>
<string name="pref_title_general_autocreonnect">Reconnect automatically</string>
</resources>

View File

@ -1,5 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.10.0" versioncode="53">
<change>Pebble: option to send sunrise and sunset events to timeline</change>
<change>Pebble: fix problems with unknown app keys while configuring watchfaces</change>
<change>Mi Band: BLE connection fixes</change>
<change>Fixes for enabling logging at whithout restarting Gadgetbridge</change>
<change>Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)</change>
<change>Display device address in device info</change>
</release>
<release version="0.9.8" versioncode="52">
<change>Pebble: fix more reconnnect issues</change>
<change>Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health</change>
<change>Pebble: option in AppManager to delete files from cache</change>
<change>Pebble: enable pbw cache and watchface configuration for Firmware 2.x</change>
<change>Pebble: allow enabling of Pebble Health without "untested features" being enabled</change>
<change>Honour "Do Not Disturb" for phone calls and SMS</change>
<change>Pebble: fix music information being messed up</change>
</release>
<release version="0.9.7" versioncode="51">
<change>Pebble: hopefully fix some reconnect issues</change>
<change>Mi Band: fix live activity monitoring running forever if back button pressed</change>

View File

@ -35,6 +35,14 @@
android:defaultValue="false"
android:key="mi_hr_sleep_detection"
android:title="@string/miband_prefs_hr_sleep_detection" />
<EditTextPreference
android:defaultValue="0"
android:inputType="numberSigned"
android:key="mi_device_time_offset_hours"
android:maxLength="2"
android:title="@string/miband_prefs_device_time_offset_hours" />
</PreferenceCategory>
<PreferenceCategory

View File

@ -23,6 +23,13 @@
android:entryValues="@array/pref_theme_values"
android:defaultValue="@string/pref_theme_value_light"
android:summary="%s" />
<ListPreference
android:key="language"
android:title="@string/pref_title_language"
android:entries="@array/pref_language_options"
android:entryValues="@array/pref_language_values"
android:defaultValue="default"
android:summary="%s" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_datetime"
@ -196,7 +203,6 @@
android:key="pref_key_pebble"
android:title="@string/pref_title_pebble_settings">
<PreferenceCategory
android:key="pref_key_general"
android:title="@string/pref_header_general">
<CheckBoxPreference
android:defaultValue="false"
@ -216,9 +222,30 @@
android:key="pebble_activitytracker"
android:title="@string/pref_title_pebble_activitytracker"
android:summary="%s" />
<CheckBoxPreference
android:title="@string/pref_title_sunrise_sunset"
android:summary="@string/pref_summary_sunrise_sunset"
android:key="send_sunrise_sunset" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_header_location">
<Preference
android:key="location_aquire"
android:title="@string/pref_title_location_aquire"/>
<EditTextPreference
android:maxLength="7"
android:digits="0123456789."
android:defaultValue="0"
android:key="location_latitude"
android:title="@string/pref_title_location_latitude" />
<EditTextPreference
android:maxLength="7"
android:digits="0123456789."
android:defaultValue="0"
android:key="location_longitude"
android:title="@string/pref_title_location_longitude" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_development"
android:title="@string/pref_header_development">
<CheckBoxPreference
android:defaultValue="false"

View File

@ -9,6 +9,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -130,6 +131,16 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {

View File

@ -0,0 +1,94 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.support.annotation.NonNull;
import junit.framework.AssertionFailedError;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
/**
* Tests dynamic enablement and disablement of file appenders.
* Test is currently disabled because logback-android does not work
* inside a plain junit test.
*/
@Ignore
public class LoggingTest {
@BeforeClass
public static void setupSuite() {
System.setProperty("logback.configurationFile", "logback.xml");
}
private Logging logging = new Logging() {
@Override
protected String createLogDirectory() throws IOException {
File dir = ensureLogFilesDir();
return dir.getAbsolutePath();
}
@NonNull
private File ensureLogFilesDir() throws IOException {
return FileUtils.createTempDir("logfiles");
}
};
@After
public void tearDown() {
assertTrue(FileUtils.deleteRecursively(getLogFilesDir()));
}
@NonNull
private File getLogFilesDir() {
String dirName = System.getProperty(Logging.PROP_LOGFILES_DIR);
if (dirName != null && dirName.length() > 5) {
File dir = new File(dirName);
return dir;
}
fail("Property " + Logging.PROP_LOGFILES_DIR + " has invalid value: " + dirName);
return null; // not reached
}
@Test
public void testToggleLogging() {
try {
File dir = getLogFilesDir();
} catch (AssertionFailedError ignored) {
// expected, as not yet set up
}
try {
logging.setupLogging(true);
File dir = getLogFilesDir();
assertEquals(1, dir.list().length);
assertNotNull(logging.getFileLogger());
assertTrue(logging.getFileLogger().isStarted());
logging.setupLogging(false);
assertNotNull(logging.getFileLogger());
assertFalse(logging.getFileLogger().isStarted());
logging.setupLogging(true);
assertNotNull(logging.getFileLogger());
assertTrue(logging.getFileLogger().isStarted());
} catch (AssertionFailedError ex) {
logging.debugLoggingConfiguration();
System.err.println(System.getProperty("java.class.path"));
throw ex;
}
}
}