mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-25 01:55:50 +01:00
Merge branch 'master' into db-refactoring
This commit is contained in:
commit
3b87966fe9
17
CHANGELOG.md
17
CHANGELOG.md
@ -1,4 +1,21 @@
|
||||
###Changelog
|
||||
####Version 0.10.0
|
||||
* Pebble: option to send sunrise and sunset events to timeline
|
||||
* Pebble: fix problems with unknown app keys while configuring watchfaces
|
||||
* Mi Band: BLE connection fixes
|
||||
* Fixes for enabling logging at whithout restarting Gadgetbridge
|
||||
* Re-enable device paring activity on Android 6 (BLE scanning needs the location preference)
|
||||
* Display device address in device info
|
||||
|
||||
####Version 0.9.8
|
||||
* Pebble: fix more reconnnect issues
|
||||
* Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health
|
||||
* Pebble: option in AppManager to delete files from cache
|
||||
* Pebble: enable pbw cache and watchface configuration for Firmware 2.x
|
||||
* Pebble: allow enabling of Pebble Health without "untested features" being enabled
|
||||
* Pebble: fix music information being messed up
|
||||
* Honour "Do Not Disturb" for phone calls and SMS
|
||||
|
||||
####Version 0.9.7
|
||||
* Pebble: hopefully fix some reconnect issues
|
||||
* Mi Band: fix live activity monitoring running forever if back button pressed
|
||||
|
12
README.md
12
README.md
@ -49,7 +49,7 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
2. Start Gadgetbridge, tap on the device you want to connect to
|
||||
3. To test, choose "Debug" from the menu and play around
|
||||
|
||||
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Getting-Started-(Pebble))
|
||||
For more information read [this wiki article](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble-Getting-Started)
|
||||
|
||||
## Features (Mi Band)
|
||||
|
||||
@ -103,10 +103,14 @@ Contributions are welcome, be it feedback, bugreports, documentation, translatio
|
||||
on any of the open [issues](https://github.com/Freeyourgadget/Gadgetbridge/issues?q=is%3Aopen+is%3Aissue);
|
||||
just leave a comment that you're working on one to avoid duplicated work.
|
||||
|
||||
Please do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting.
|
||||
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or manually.
|
||||
|
||||
Translations can be contributed via https://www.transifex.com/projects/p/gadgetbridge/resource/strings/ or
|
||||
manually.
|
||||
## Do you have further questions or feedback?
|
||||
|
||||
Feel free to open an issue on our issue tracker, but please:
|
||||
- do not use the issue tracker as a forum, do not ask for ETAs and read the issue conversation before posting
|
||||
- use the search functionality to ensure that your questions wasn't already answered. Don't forget to check the **closed** issues as well!
|
||||
- remember that this is a community project, people are contributing in their free time because they like doing so: don't take the fun away! Be kind and constructive.
|
||||
|
||||
|
||||
## Having problems?
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -47,7 +48,6 @@ public class AppManagerActivity extends GBActivity {
|
||||
if (action.equals(GBApplication.ACTION_QUIT)) {
|
||||
finish();
|
||||
} else if (action.equals(ACTION_REFRESH_APPLIST)) {
|
||||
appList.clear();
|
||||
int appCount = intent.getIntExtra("app_count", 0);
|
||||
for (Integer i = 0; i < appCount; i++) {
|
||||
String appName = intent.getStringExtra("app_name" + i.toString());
|
||||
@ -55,11 +55,21 @@ public class AppManagerActivity extends GBActivity {
|
||||
UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString()));
|
||||
GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)];
|
||||
|
||||
appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
|
||||
}
|
||||
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
appList.addAll(getSystemApps());
|
||||
boolean found = false;
|
||||
for (final ListIterator<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);
|
||||
}
|
||||
}
|
||||
|
||||
mGBDeviceAppAdapter.notifyDataSetChanged();
|
||||
@ -76,8 +86,10 @@ public class AppManagerActivity extends GBActivity {
|
||||
|
||||
private List<GBDeviceApp> getSystemApps() {
|
||||
List<GBDeviceApp> systemApps = new ArrayList<>();
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) {
|
||||
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
@ -149,9 +161,7 @@ public class AppManagerActivity extends GBActivity {
|
||||
|
||||
appList.addAll(getCachedApps());
|
||||
|
||||
if (prefs.getBoolean("pebble_force_untested", false)) {
|
||||
appList.addAll(getSystemApps());
|
||||
}
|
||||
appList.addAll(getSystemApps());
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
@ -171,11 +181,13 @@ public class AppManagerActivity extends GBActivity {
|
||||
|
||||
if (!selectedApp.isInCache()) {
|
||||
menu.removeItem(R.id.appmanager_app_reinstall);
|
||||
menu.removeItem(R.id.appmanager_app_delete_cache);
|
||||
}
|
||||
if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
|
||||
menu.removeItem(R.id.appmanager_health_activate);
|
||||
menu.removeItem(R.id.appmanager_health_deactivate);
|
||||
} else if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) {
|
||||
}
|
||||
if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) {
|
||||
menu.removeItem(R.id.appmanager_app_delete);
|
||||
}
|
||||
if (!selectedApp.isConfigurable()) {
|
||||
@ -184,10 +196,42 @@ public class AppManagerActivity extends GBActivity {
|
||||
menu.setHeaderTitle(selectedApp.getName());
|
||||
}
|
||||
|
||||
private void removeAppFromList(UUID uuid) {
|
||||
for (final ListIterator<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));
|
||||
|
@ -353,11 +353,7 @@ public class ControlCenter extends GBActivity {
|
||||
}
|
||||
|
||||
private void launchDiscoveryActivity() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS));
|
||||
} else {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
}
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -129,10 +129,6 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device.getFirmwareVersion().startsWith("v3")) {
|
||||
return;
|
||||
}
|
||||
|
||||
File destDir;
|
||||
GBDeviceApp app = mPBWReader.getGBDeviceApp();
|
||||
try {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 "?";
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
@ -360,10 +361,18 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
LOG.info("Attempting to set wear location...");
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
|
||||
if (characteristic != null) {
|
||||
int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
|
||||
transaction.write(characteristic, new byte[]{
|
||||
MiBandService.COMMAND_SET_WEAR_LOCATION,
|
||||
(byte) location
|
||||
transaction.add(new ConditionalWriteAction(characteristic) {
|
||||
@Override
|
||||
protected byte[] checkCondition() {
|
||||
if (getDeviceInfo() != null && getDeviceInfo().isAmazFit()) {
|
||||
return null;
|
||||
}
|
||||
int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
|
||||
return new byte[]{
|
||||
MiBandService.COMMAND_SET_WEAR_LOCATION,
|
||||
(byte) location
|
||||
};
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOG.info("Unable to set Wear Location");
|
||||
@ -382,6 +391,16 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of device initialization process. Do not call manually.
|
||||
*
|
||||
@ -834,6 +853,9 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
private void handleHeartrate(byte[] value) {
|
||||
if (value.length == 2 && value[0] == 6) {
|
||||
int hrValue = (value[1] & 0xff);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("heart rate: " + hrValue);
|
||||
}
|
||||
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
|
||||
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
|
||||
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
|
||||
|
@ -23,6 +23,10 @@ public class AppMessageHandler {
|
||||
mPebbleProtocol = pebbleProtocol;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public UUID getUUID() {
|
||||
return mUUID;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -15,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
class DatalogSessionHealthSleep extends DatalogSession {
|
||||
class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class);
|
||||
|
||||
@ -27,65 +25,11 @@ class DatalogSessionHealthSleep extends DatalogSession {
|
||||
@Override
|
||||
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
|
||||
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
|
||||
switch (this.tag) {
|
||||
case 83:
|
||||
return handleMessage83(datalogMessage, length);
|
||||
case 84:
|
||||
return handleMessage84(datalogMessage, length);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleMessage84(ByteBuffer datalogMessage, int length) {
|
||||
int initialPosition = datalogMessage.position();
|
||||
int beginOfRecordPosition;
|
||||
short recordVersion; //probably
|
||||
short recordType; //probably: 1=sleep, 2=deep sleep
|
||||
|
||||
if (0 != (length % itemSize))
|
||||
return false;//malformed message?
|
||||
|
||||
int recordCount = length / itemSize;
|
||||
SleepRecord84[] sleepRecords = new SleepRecord84[recordCount];
|
||||
|
||||
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
|
||||
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
|
||||
datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
|
||||
recordVersion = datalogMessage.getShort();
|
||||
if (recordVersion != 1)
|
||||
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
|
||||
|
||||
datalogMessage.getShort();//throwaway, unknown
|
||||
recordType = datalogMessage.getShort();
|
||||
|
||||
sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt());
|
||||
if (!isPebbleHealthEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
|
||||
}
|
||||
|
||||
private boolean store84(SleepRecord84[] sleepRecords) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
|
||||
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
|
||||
for (SleepRecord84 sleepRecord : sleepRecords) {
|
||||
if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds))
|
||||
return false;
|
||||
if (sleepRecord.type == 2) {
|
||||
sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP));
|
||||
} else {
|
||||
sampleProvider.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleMessage83(ByteBuffer datalogMessage, int length) {
|
||||
int initialPosition = datalogMessage.position();
|
||||
int beginOfRecordPosition;
|
||||
short recordVersion; //probably
|
||||
@ -94,7 +38,7 @@ class DatalogSessionHealthSleep extends DatalogSession {
|
||||
return false;//malformed message?
|
||||
|
||||
int recordCount = length / itemSize;
|
||||
SleepRecord83[] sleepRecords = new SleepRecord83[recordCount];
|
||||
SleepRecord[] sleepRecords = new SleepRecord[recordCount];
|
||||
|
||||
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
|
||||
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
|
||||
@ -103,21 +47,20 @@ class DatalogSessionHealthSleep extends DatalogSession {
|
||||
if (recordVersion != 1)
|
||||
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
|
||||
|
||||
sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(),
|
||||
sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(),
|
||||
datalogMessage.getInt(),
|
||||
datalogMessage.getInt(),
|
||||
datalogMessage.getInt());
|
||||
}
|
||||
|
||||
return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
|
||||
return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
|
||||
}
|
||||
|
||||
private boolean store83(SleepRecord83[] sleepRecords) {
|
||||
private boolean store(SleepRecord[] sleepRecords) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
|
||||
GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO);
|
||||
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
|
||||
for (SleepRecord83 sleepRecord : sleepRecords) {
|
||||
for (SleepRecord sleepRecord : sleepRecords) {
|
||||
if (latestTimestamp < sleepRecord.bedTimeEnd)
|
||||
return false;
|
||||
sampleProvider.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
|
||||
@ -128,13 +71,13 @@ class DatalogSessionHealthSleep extends DatalogSession {
|
||||
return true;
|
||||
}
|
||||
|
||||
private class SleepRecord83 {
|
||||
private class SleepRecord {
|
||||
int offsetUTC; //probably
|
||||
int bedTimeStart;
|
||||
int bedTimeEnd;
|
||||
int deepSleepSeconds;
|
||||
|
||||
public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
|
||||
public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
|
||||
this.offsetUTC = offsetUTC;
|
||||
this.bedTimeStart = bedTimeStart;
|
||||
this.bedTimeEnd = bedTimeEnd;
|
||||
@ -142,17 +85,4 @@ class DatalogSessionHealthSleep extends DatalogSession {
|
||||
}
|
||||
}
|
||||
|
||||
private class SleepRecord84 {
|
||||
int type; //1=sleep, 2=deep sleep
|
||||
int offsetUTC; //probably
|
||||
int timestampStart;
|
||||
int durationSeconds;
|
||||
|
||||
public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) {
|
||||
this.type = type;
|
||||
this.offsetUTC = offsetUTC;
|
||||
this.timestampStart = timestampStart;
|
||||
this.durationSeconds = durationSeconds;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothServerSocket;
|
||||
import android.bluetooth.BluetoothSocket;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@ -48,9 +47,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
public class PebbleIoThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
|
||||
|
||||
private static final UUID PEBBLE_UUID_RECONNECT = UUID.fromString("00000000-deca-fade-deca-deafdecacafe");
|
||||
private static final UUID PEBBLE_UUID_RECONNECT3X = UUID.fromString("a924496e-cc7c-4dff-8a9f-9a76cc2e9d50");
|
||||
|
||||
public static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED";
|
||||
public static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED";
|
||||
public static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK";
|
||||
@ -71,7 +67,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
private boolean mIsTCP = false;
|
||||
private BluetoothAdapter mBtAdapter = null;
|
||||
private BluetoothSocket mBtSocket = null;
|
||||
private BluetoothServerSocket mBtServerSocket = null;
|
||||
private Socket mTCPSocket = null; // for emulator
|
||||
private InputStream mInStream = null;
|
||||
private OutputStream mOutStream = null;
|
||||
@ -166,6 +161,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
@Override
|
||||
protected boolean connect(String btDeviceAddress) {
|
||||
GBDevice.State originalState = gbDevice.getState();
|
||||
gbDevice.setState(GBDevice.State.CONNECTING);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
try {
|
||||
// contains only one ":"? then it is addr:port
|
||||
int firstColon = btDeviceAddress.indexOf(":");
|
||||
@ -193,6 +190,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
gbDevice.setState(originalState);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
mInStream = null;
|
||||
mOutStream = null;
|
||||
mBtSocket = null;
|
||||
@ -202,12 +201,8 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
mPebbleProtocol.setForceProtocol(prefs.getBoolean("pebble_force_protocol", false));
|
||||
|
||||
mIsConnected = true;
|
||||
if (originalState == GBDevice.State.WAITING_FOR_RECONNECT) {
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
} else {
|
||||
gbDevice.setState(GBDevice.State.CONNECTED);
|
||||
write(mPebbleProtocol.encodeFirmwareVersionReq());
|
||||
}
|
||||
write(mPebbleProtocol.encodeFirmwareVersionReq());
|
||||
gbDevice.setState(GBDevice.State.CONNECTED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
return true;
|
||||
@ -215,15 +210,18 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
gbDevice.setState(GBDevice.State.CONNECTING);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
mIsConnected = connect(gbDevice.getAddress());
|
||||
enablePebbleKitReceiver(mIsConnected);
|
||||
mQuit = !mIsConnected; // quit if not connected
|
||||
if (!mIsConnected) {
|
||||
if (GBApplication.getGBPrefs().getAutoReconnect()) {
|
||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
enablePebbleKitReceiver(true);
|
||||
mQuit = false;
|
||||
while (!mQuit) {
|
||||
try {
|
||||
if (mIsInstalling) {
|
||||
@ -365,9 +363,10 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
LOG.info(e.getMessage());
|
||||
mIsConnected = false;
|
||||
int reconnectAttempts = prefs.getInt("pebble_reconnect_attempts", 10);
|
||||
if (GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) {
|
||||
gbDevice.setState(GBDevice.State.CONNECTING);
|
||||
if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) {
|
||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
int delaySeconds = 1;
|
||||
while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) {
|
||||
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
|
||||
@ -383,33 +382,10 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mIsConnected && !mQuit) {
|
||||
try {
|
||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
UUID reconnectUUID = mPebbleProtocol.isFw3x ? PEBBLE_UUID_RECONNECT3X : PEBBLE_UUID_RECONNECT;
|
||||
mBtServerSocket = mBtAdapter.listenUsingRfcommWithServiceRecord("PebbleReconnectListener", reconnectUUID);
|
||||
mBtSocket = mBtServerSocket.accept();
|
||||
LOG.info("incoming connection on reconnect uuid (" + reconnectUUID + "), will connect actively");
|
||||
mBtSocket.close();
|
||||
mIsConnected = connect(gbDevice.getAddress());
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
LOG.info("error while reconnecting");
|
||||
} finally {
|
||||
try {
|
||||
if (mBtServerSocket != null) {
|
||||
mBtServerSocket.close();
|
||||
mBtServerSocket = null;
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mIsConnected) {
|
||||
mBtSocket = null;
|
||||
LOG.info("Bluetooth socket closed, will quit IO Thread");
|
||||
mQuit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,10 +397,16 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
mBtSocket = null;
|
||||
}
|
||||
|
||||
enablePebbleKitReceiver(false);
|
||||
mBtSocket = null;
|
||||
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
|
||||
|
||||
if (mQuit) {
|
||||
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
|
||||
} else {
|
||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
}
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
@ -700,13 +682,6 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (mBtServerSocket != null) {
|
||||
try {
|
||||
mBtServerSocket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (mTCPSocket != null) {
|
||||
try {
|
||||
mTCPSocket.close();
|
||||
|
@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
@ -241,6 +242,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
static final byte LENGTH_UUID = 16;
|
||||
|
||||
static final long GB_UUID_MASK = 0x4767744272646700L;
|
||||
|
||||
// base is -5
|
||||
private static final String[] hwRevisions = {
|
||||
// Emulator
|
||||
@ -452,7 +455,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
if (isFw3x) {
|
||||
// 3.x notification
|
||||
//return encodeTimelinePin(id, (int) ((ts + 600) & 0xffffffffL), (short) 90, PebbleIconID.TIMELINE_CALENDAR, title); // really, this is just for testing
|
||||
return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.cannedReplies);
|
||||
} else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) {
|
||||
// 2.x notification
|
||||
@ -465,6 +467,29 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong();
|
||||
int iconId;
|
||||
switch (calendarEventSpec.type) {
|
||||
case CalendarEventSpec.TYPE_SUNRISE:
|
||||
iconId = PebbleIconID.SUNRISE;
|
||||
break;
|
||||
case CalendarEventSpec.TYPE_SUNSET:
|
||||
iconId = PebbleIconID.SUNSET;
|
||||
break;
|
||||
default:
|
||||
iconId = PebbleIconID.TIMELINE_CALENDAR;
|
||||
}
|
||||
|
||||
return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) calendarEventSpec.durationInSeconds, iconId, calendarEventSpec.title, calendarEventSpec.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeDeleteCalendarEvent(byte type, long id) {
|
||||
return encodeBlobdb(new UUID(GB_UUID_MASK | type, id), BLOBDB_DELETE, BLOBDB_PIN, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeSetTime() {
|
||||
long ts = System.currentTimeMillis();
|
||||
@ -743,11 +768,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
private byte[] encodeTimelinePin(int id, int timestamp, short duration, int icon_id, String title, String subtitle) {
|
||||
private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, String title, String subtitle) {
|
||||
final short TIMELINE_PIN_LENGTH = 46;
|
||||
|
||||
icon_id |= 0x80000000;
|
||||
UUID uuid = new UUID(mRandom.nextLong(), ((long) mRandom.nextInt() << 32) | id);
|
||||
byte attributes_count = 2;
|
||||
byte actions_count = 0;
|
||||
|
||||
@ -1235,10 +1259,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
buf.put(PHONEVERSION_APPVERSION_MAGIC);
|
||||
buf.put((byte) 3); // major
|
||||
buf.put((byte) 8); // minor
|
||||
buf.put((byte) 1); // patch
|
||||
buf.put((byte) 12); // minor
|
||||
buf.put((byte) 0); // patch
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.putLong(0x00000000000000af); //flags
|
||||
buf.putLong(0x00000000000001af); //flags
|
||||
|
||||
return buf.array();
|
||||
}
|
||||
@ -1803,7 +1827,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
LOG.info(ENDPOINT_NAME + ": (cmd:" + command + ")" + uuid);
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
return new GBDeviceEvent[]{null};
|
||||
}
|
||||
|
||||
private GBDeviceEvent decodeBlobDb(ByteBuffer buf) {
|
||||
@ -1881,9 +1905,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
|
||||
if (!mDatalogSessions.containsKey(id)) {
|
||||
if (uuid.equals(UUID_ZERO) && log_tag == 81) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice()));
|
||||
} else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice())));
|
||||
} else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size));
|
||||
} else if (uuid.equals(UUID_ZERO) && log_tag == 84) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size));
|
||||
} else {
|
||||
mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size));
|
||||
}
|
||||
@ -2091,8 +2117,12 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
AppMessageHandler handler = mAppMessageHandlers.get(uuid);
|
||||
if (handler != null) {
|
||||
ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
|
||||
devEvts = handler.handleMessage(dict);
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user