diff --git a/CHANGELOG.md b/CHANGELOG.md index bf801f26..91ce3b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ###Changelog +####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 +* Honour "Do Not Disturb" for phone calls and SMS + ####Version 0.9.7 * Pebble: hopefully fix some reconnect issues * Mi Band: fix live activity monitoring running forever if back button pressed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 00000000..1637e814 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,28 @@ + Andreas Shimokawa + cpfeiffer + Daniele Gobbetti + Daniele Gobbetti + danielegobbetti + Carsten Pfeiffer + Julien Pivotto + Lem Dulfo + Sergey Trofimov + Daniele Gobbetti + cpfeiffer + 0nse <0nse@users.noreply.github.com> + Christian Fischer + Normano64 + Ⲇⲁⲛⲓ Φi + xphnx + Tarik Sekmen + rober + Nicolò Balzarotti + Marc Schlaich + kevlarcade + Kasha + Chris Perelstein + Alexey Afanasev + +And all the Transifex translators, which I cannot automatically list, at the moment. + +git log --raw | grep "^Author: " | sort | uniq -c | sort -k 1 -n -r | cut -f 2- -d: > CONTRIBUTORS.md diff --git a/app/build.gradle b/app/build.gradle index fffe7f3c..c30ca034 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.9.7" - versionCode 51 + versionName "0.9.8" + versionCode 52 } buildTypes { release { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index e17c4584..073689a8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -1,15 +1,21 @@ package nodomain.freeyourgadget.gadgetbridge; +import android.annotation.TargetApi; import android.app.Application; +import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Resources; +import android.database.Cursor; +import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.preference.PreferenceManager; +import android.provider.ContactsContract.PhoneLookup; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.util.TypedValue; @@ -59,6 +65,10 @@ public class GBApplication extends Application { private static Appender fileLogger; private static Prefs prefs; private static GBPrefs gbPrefs; + /** + * Note: is null on Lollipop and Kitkat + */ + private static NotificationManager notificationManager; public static final String ACTION_QUIT = "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit"; @@ -119,6 +129,10 @@ public class GBApplication extends Application { filterLocal.addAction(ACTION_QUIT); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); + if (isRunningMarshmallowOrLater()) { + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + // for testing DB stuff // SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase(); // db.close(); @@ -247,6 +261,63 @@ public class GBApplication extends Application { return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; } + public static boolean isRunningMarshmallowOrLater() { + return VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + + private static boolean isPrioritySender(int prioritySenders, String number) { + if (prioritySenders == Policy.PRIORITY_SENDERS_ANY) { + return true; + } else { + Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + String[] projection = new String[]{PhoneLookup._ID, PhoneLookup.STARRED}; + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); + boolean exists = false; + int starred = 0; + try { + if (cursor != null && cursor.moveToFirst()) { + exists = true; + starred = cursor.getInt(cursor.getColumnIndexOrThrow(PhoneLookup.STARRED)); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS && exists) { + return true; + } else if (prioritySenders == Policy.PRIORITY_SENDERS_STARRED && starred == 1) { + return true; + } + return false; + } + } + + @TargetApi(Build.VERSION_CODES.M) + public static boolean isPriorityNumber(int priorityType, String number) { + NotificationManager.Policy notificationPolicy = notificationManager.getNotificationPolicy(); + if(priorityType == Policy.PRIORITY_CATEGORY_MESSAGES) { + if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) == Policy.PRIORITY_CATEGORY_MESSAGES) { + return isPrioritySender(notificationPolicy.priorityMessageSenders, number); + } + } else if (priorityType == Policy.PRIORITY_CATEGORY_CALLS) { + if ((notificationPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) == Policy.PRIORITY_CATEGORY_CALLS) { + return isPrioritySender(notificationPolicy.priorityCallSenders, number); + } + } + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public static int getGrantedInterruptionFilter() { + if (prefs.getBoolean("notification_filter", false) && GBApplication.isRunningMarshmallowOrLater()) { + if (notificationManager.isNotificationPolicyAccessGranted()) { + return notificationManager.getCurrentInterruptionFilter(); + } + } + return NotificationManager.INTERRUPTION_FILTER_ALL; + } + public static HashSet blacklist = null; private static void loadBlackList() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index 2e6bbba0..f66bd731 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -47,7 +48,6 @@ public class AppManagerActivity extends GBActivity { if (action.equals(GBApplication.ACTION_QUIT)) { finish(); } else if (action.equals(ACTION_REFRESH_APPLIST)) { - appList.clear(); int appCount = intent.getIntExtra("app_count", 0); for (Integer i = 0; i < appCount; i++) { String appName = intent.getStringExtra("app_name" + i.toString()); @@ -55,11 +55,21 @@ public class AppManagerActivity extends GBActivity { UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString())); GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)]; - appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType)); - } - - if (prefs.getBoolean("pebble_force_untested", false)) { - appList.addAll(getSystemApps()); + boolean found = false; + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + app.setOnDevice(true); + iter.set(app); + found = true; + break; + } + } + if (!found) { + GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType); + app.setOnDevice(true); + appList.add(app); + } } mGBDeviceAppAdapter.notifyDataSetChanged(); @@ -76,8 +86,10 @@ public class AppManagerActivity extends GBActivity { private List getSystemApps() { List systemApps = new ArrayList<>(); - systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); - systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + if (prefs.getBoolean("pebble_force_untested", false)) { + systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + } if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); } @@ -149,9 +161,7 @@ public class AppManagerActivity extends GBActivity { appList.addAll(getCachedApps()); - if (prefs.getBoolean("pebble_force_untested", false)) { - appList.addAll(getSystemApps()); - } + appList.addAll(getSystemApps()); IntentFilter filter = new IntentFilter(); filter.addAction(GBApplication.ACTION_QUIT); @@ -171,11 +181,13 @@ public class AppManagerActivity extends GBActivity { if (!selectedApp.isInCache()) { menu.removeItem(R.id.appmanager_app_reinstall); + menu.removeItem(R.id.appmanager_app_delete_cache); } if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { menu.removeItem(R.id.appmanager_health_activate); menu.removeItem(R.id.appmanager_health_deactivate); - } else if (PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { + } + if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) { menu.removeItem(R.id.appmanager_app_delete); } if (!selectedApp.isConfigurable()) { @@ -184,10 +196,42 @@ public class AppManagerActivity extends GBActivity { menu.setHeaderTitle(selectedApp.getName()); } + private void removeAppFromList(UUID uuid) { + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + iter.remove(); + mGBDeviceAppAdapter.notifyDataSetChanged(); + break; + } + } + } + @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.appmanager_health_deactivate: + case R.id.appmanager_app_delete_cache: + String baseName; + try { + baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); + } catch (IOException e) { + LOG.warn("could not get external dir while trying to access pbw cache."); + return true; + } + + String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"}; + + for (String suffix : suffixToDelete) { + File fileToDelete = new File(baseName + suffix); + if (!fileToDelete.delete()) { + LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString()); + } else { + LOG.info("deleted file: " + fileToDelete.toString()); + } + } + removeAppFromList(selectedApp.getUUID()); + // fall through case R.id.appmanager_app_delete: GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); return true; @@ -196,7 +240,7 @@ public class AppManagerActivity extends GBActivity { try { cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); } catch (IOException e) { - LOG.warn("could not get external dir while reading pbw cache."); + LOG.warn("could not get external dir while trying to access pbw cache."); return true; } GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath)); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index c8bfe835..c6479695 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -6,6 +6,7 @@ import android.content.pm.ResolveInfo; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; @@ -107,6 +108,12 @@ public class SettingsActivity extends AbstractSettingsActivity { }); + if (!GBApplication.isRunningMarshmallowOrLater()) { + pref = findPreference("notification_filter"); + PreferenceCategory category = (PreferenceCategory) findPreference("pref_key_notifications"); + category.removePreference(pref); + } + // Get all receivers of Media Buttons Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java index 5a813792..d4ae263f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java @@ -41,7 +41,15 @@ public class GBDeviceAppAdapter extends ArrayAdapter { ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image); deviceAppVersionAuthorLabel.setText(getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator())); - deviceAppNameLabel.setText(deviceApp.getName()); + + // FIXME: replace with small icons + String appNameLabelText = deviceApp.getName(); + if (deviceApp.isInCache() || deviceApp.isOnDevice()) { + appNameLabelText += " (" + (deviceApp.isInCache() ? "C" : "") + + (deviceApp.isOnDevice() ? "D" : "") + ")"; + } + deviceAppNameLabel.setText(appNameLabelText); + switch (deviceApp.getType()) { case APP_GENERIC: deviceImageView.setImageResource(R.drawable.ic_watchapp); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java index 30becd11..9d66c0db 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java @@ -129,10 +129,6 @@ public class PBWInstallHandler implements InstallHandler { return; } - if (!device.getFirmwareVersion().startsWith("v3")) { - return; - } - File destDir; GBDeviceApp app = mPBWReader.getGBDeviceApp(); try { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java new file mode 100644 index 00000000..5282e7a5 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothConnectReceiver.java @@ -0,0 +1,43 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; + +public class BluetoothConnectReceiver extends BroadcastReceiver { + + private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.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())) { + LOG.info("will connect to " + gbDevice.getName()); + GBApplication.deviceService().connect(); + } else { + LOG.info("won't connect to " + device.getAddress() + "(" + device.getName() + ")"); + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java index 899ee0c6..439c34b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java @@ -1,5 +1,6 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; +import android.app.NotificationManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,6 +34,14 @@ public class K9Receiver extends BroadcastReceiver { return; } } + switch (GBApplication.getGrantedInterruptionFilter()) { + case NotificationManager.INTERRUPTION_FILTER_ALL: + break; + case NotificationManager.INTERRUPTION_FILTER_ALARMS: + case NotificationManager.INTERRUPTION_FILTER_NONE: + case NotificationManager.INTERRUPTION_FILTER_PRIORITY: + return; + } String uriWanted = intent.getData().toString(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 9f93618b..a3c3d37c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -167,6 +168,16 @@ public class NotificationListener extends NotificationListenerService { return; } } + switch (GBApplication.getGrantedInterruptionFilter()) { + case NotificationManager.INTERRUPTION_FILTER_ALL: + break; + case NotificationManager.INTERRUPTION_FILTER_ALARMS: + case NotificationManager.INTERRUPTION_FILTER_NONE: + return; + case NotificationManager.INTERRUPTION_FILTER_PRIORITY: + // FIXME: Handle Reminders and Events if they are enabled in Do Not Disturb + return; + } String source = sbn.getPackageName(); Notification notification = sbn.getNotification(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java index 496872db..29693538 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; +import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -67,6 +69,19 @@ public class PhoneCallReceiver extends BroadcastReceiver { if ("never".equals(prefs.getString("notification_mode_calls", "always"))) { return; } + switch (GBApplication.getGrantedInterruptionFilter()) { + case NotificationManager.INTERRUPTION_FILTER_ALL: + break; + case NotificationManager.INTERRUPTION_FILTER_ALARMS: + case NotificationManager.INTERRUPTION_FILTER_NONE: + return; + case NotificationManager.INTERRUPTION_FILTER_PRIORITY: + if (GBApplication.isPriorityNumber(Policy.PRIORITY_CATEGORY_CALLS, mSavedNumber)) { + break; + } + // FIXME: Handle Repeat callers if it is enabled in Do Not Disturb + return; + } CallSpec callSpec = new CallSpec(); callSpec.number = mSavedNumber; callSpec.command = callCommand; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java index 0404cbc2..5df07c8d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; +import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -41,6 +43,18 @@ public class SMSReceiver extends BroadcastReceiver { notificationSpec.body = message.getDisplayMessageBody(); notificationSpec.phoneNumber = message.getOriginatingAddress(); if (notificationSpec.phoneNumber != null) { + switch (GBApplication.getGrantedInterruptionFilter()) { + case NotificationManager.INTERRUPTION_FILTER_ALL: + break; + case NotificationManager.INTERRUPTION_FILTER_ALARMS: + case NotificationManager.INTERRUPTION_FILTER_NONE: + return; + case NotificationManager.INTERRUPTION_FILTER_PRIORITY: + if (GBApplication.isPriorityNumber(Policy.PRIORITY_CATEGORY_MESSAGES, notificationSpec.phoneNumber)) { + break; + } + return; + } GBApplication.deviceService().onNotification(notificationSpec); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java index d2280d17..f5587645 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java @@ -12,6 +12,7 @@ public class GBDeviceApp { private final UUID uuid; private final Type type; private final boolean inCache; + private boolean isOnDevice; private final boolean configurable; public GBDeviceApp(UUID uuid, String name, String creator, String version, Type type) { @@ -23,6 +24,7 @@ public class GBDeviceApp { //FIXME: do not assume this.inCache = false; this.configurable = false; + this.isOnDevice = false; } public GBDeviceApp(JSONObject json, boolean configurable) { @@ -52,10 +54,18 @@ public class GBDeviceApp { this.configurable = configurable; } + public void setOnDevice(boolean isOnDevice) { + this.isOnDevice = isOnDevice; + } + public boolean isInCache() { return inCache; } + public boolean isOnDevice() { + return isOnDevice; + } + public String getName() { return name; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 7738f40c..37d217f8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service; import android.app.NotificationManager; import android.app.Service; +import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,6 +25,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.K9Receiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver; @@ -105,6 +107,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private PebbleReceiver mPebbleReceiver = null; private MusicPlaybackReceiver mMusicPlaybackReceiver = null; private TimeChangeReceiver mTimeChangeReceiver = null; + private BluetoothConnectReceiver mBlueToothConnectReceiver = null; private Random mRandom = new Random(); @@ -279,6 +282,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 +465,10 @@ 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)); + } } else { if (mPhoneCallReceiver != null) { unregisterReceiver(mPhoneCallReceiver); @@ -482,6 +494,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mTimeChangeReceiver); mTimeChangeReceiver = null; } + if (mBlueToothConnectReceiver != null) { + unregisterReceiver(mBlueToothConnectReceiver); + mBlueToothConnectReceiver = null; + } } } @@ -549,4 +565,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere public GBPrefs getGBPrefs() { return GBApplication.getGBPrefs(); } + + public GBDevice getGBDevice() { + return mGBDevice; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java new file mode 100644 index 00000000..b7d42544 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java @@ -0,0 +1,99 @@ +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 DatalogSession { + + 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)); + + int initialPosition = datalogMessage.position(); + int beginOfRecordPosition; + short recordVersion; //probably + short recordType; //probably: 1=sleep, 2=deep sleep, 5=??run??ignored for now + + if (0 != (length % itemSize)) + return false;//malformed message? + + int recordCount = length / itemSize; + OverlayRecord[] overlayRecords = new OverlayRecord[recordCount]; + + for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) { + beginOfRecordPosition = initialPosition + recordIdx * itemSize; + datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record + recordVersion = datalogMessage.getShort(); + if ((recordVersion != 1) && (recordVersion != 3)) + return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it + + datalogMessage.getShort();//throwaway, unknown + recordType = datalogMessage.getShort(); + + overlayRecords[recordIdx] = new OverlayRecord(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt()); + } + + return store(overlayRecords);//NACK if we cannot store the data yet, the watch will send the overlay records again. + } + + private boolean store(OverlayRecord[] overlayRecords) { + DBHandler dbHandler = null; + SampleProvider sampleProvider = new HealthSampleProvider(); + try { + dbHandler = GBApplication.acquireDB(); + int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); + for (OverlayRecord overlayRecord : overlayRecords) { + if (latestTimestamp < (overlayRecord.timestampStart + overlayRecord.durationSeconds)) + return false; + switch (overlayRecord.type) { + case 1: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); + break; + case 2: + dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); + break; + default: + //TODO: other values refer to unknown activity types. + } + } + } catch (Exception ex) { + LOG.debug(ex.getMessage()); + } finally { + if (dbHandler != null) { + dbHandler.release(); + } + } + return true; + } + + private class OverlayRecord { + int type; //1=sleep, 2=deep sleep + int offsetUTC; //probably + int timestampStart; + int durationSeconds; + + public OverlayRecord(int type, int offsetUTC, int timestampStart, int durationSeconds) { + this.type = type; + this.offsetUTC = offsetUTC; + this.timestampStart = timestampStart; + this.durationSeconds = durationSeconds; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java index 7d06876f..2162f9d5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; -import android.widget.Toast; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,71 +25,6 @@ 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()); - } - - return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again. - } - - private boolean store84(SleepRecord84[] sleepRecords) { - DBHandler dbHandler = null; - SampleProvider sampleProvider = new HealthSampleProvider(); - try { - dbHandler = GBApplication.acquireDB(); - int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); - for (SleepRecord84 sleepRecord : sleepRecords) { - if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds)) - return false; - if (sleepRecord.type == 2) { - dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider); - } else { - dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); - } - - } - } catch (Exception ex) { - LOG.debug(ex.getMessage()); - } finally { - if (dbHandler != null) { - dbHandler.release(); - } - } - return true; - } - - private boolean handleMessage83(ByteBuffer datalogMessage, int length) { int initialPosition = datalogMessage.position(); int beginOfRecordPosition; short recordVersion; //probably @@ -100,7 +33,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; @@ -109,23 +42,22 @@ 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) { DBHandler dbHandler = null; SampleProvider sampleProvider = new HealthSampleProvider(); - GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO); try { dbHandler = GBApplication.acquireDB(); int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider); - for (SleepRecord83 sleepRecord : sleepRecords) { + for (SleepRecord sleepRecord : sleepRecords) { if (latestTimestamp < sleepRecord.bedTimeEnd) return false; dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider); @@ -140,13 +72,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; @@ -154,17 +86,4 @@ class DatalogSessionHealthSleep extends DatalogSession { } } - private class SleepRecord84 { - int type; //1=sleep, 2=deep sleep - int offsetUTC; //probably - int timestampStart; - int durationSeconds; - - public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) { - this.type = type; - this.offsetUTC = offsetUTC; - this.timestampStart = timestampStart; - this.durationSeconds = durationSeconds; - } - } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 43b86e2f..a14fb496 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; @@ -48,9 +47,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class PebbleIoThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class); - private static final UUID PEBBLE_UUID_RECONNECT = UUID.fromString("00000000-deca-fade-deca-deafdecacafe"); - private static final UUID PEBBLE_UUID_RECONNECT3X = UUID.fromString("a924496e-cc7c-4dff-8a9f-9a76cc2e9d50"); - public static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED"; public static final String PEBBLEKIT_ACTION_PEBBLE_DISCONNECTED = "com.getpebble.action.PEBBLE_DISCONNECTED"; public static final String PEBBLEKIT_ACTION_APP_ACK = "com.getpebble.action.app.ACK"; @@ -71,7 +67,6 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean mIsTCP = false; private BluetoothAdapter mBtAdapter = null; private BluetoothSocket mBtSocket = null; - private BluetoothServerSocket mBtServerSocket = null; private Socket mTCPSocket = null; // for emulator private InputStream mInStream = null; private OutputStream mOutStream = null; @@ -365,7 +360,7 @@ 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) { + if (!mQuit && GBApplication.getGBPrefs().getAutoReconnect() && reconnectAttempts > 0) { gbDevice.setState(GBDevice.State.CONNECTING); gbDevice.sendDeviceUpdateIntent(getContext()); int delaySeconds = 1; @@ -383,33 +378,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 +393,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 +678,6 @@ public class PebbleIoThread extends GBDeviceIoThread { e.printStackTrace(); } } - if (mBtServerSocket != null) { - try { - mBtServerSocket.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } if (mTCPSocket != null) { try { mTCPSocket.close(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index ad947bcf..52adb045 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -1883,8 +1883,10 @@ public class PebbleProtocol extends GBDeviceProtocol { 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)); - } else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) { + } 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)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index 74e4736d..e396d03f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -78,6 +78,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()) { diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml index dd5d22c3..873e0c24 100644 --- a/app/src/main/res/menu/appmanager_context.xml +++ b/app/src/main/res/menu/appmanager_context.xml @@ -6,6 +6,9 @@ + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 121df48e..1588000a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,6 +14,7 @@ App Manager Löschen + Löschen und aus dem Zwischenspeicher entfernen Sperre für Benachrichtigungen @@ -44,6 +45,8 @@ Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden. Andere Benachrichtigungen … auch wenn der Bildschirm an ist + Bitte nicht stören + Stoppe unerwünschte Nachrichten, wenn im \"Nicht Stören\"-Modus immer wenn der Bildschirm aus ist niemals @@ -202,8 +205,9 @@ Aktivitätsdaten auf dem Gerät lassen Inkompatible Firmware Diese Firmware ist nicht mit dem Gerät kompatibel + Wecker für zukünftige Ereignisse vormerken Verwende den Herzfrequenzsensor um die Schlaferkennung zu verbessern - warte auf eingehende Verbindung + warte auf Verbindung Erneut installieren Über Dich Geburtsjahr @@ -214,6 +218,7 @@ deaktivieren Authentifiziere Authentifizierung erforderlich + Konfigurieren Zzz Widget hinzufügen Gewünschte Schlafdauer in Stunden diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6dc1e3cc..adf7da93 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -69,8 +69,6 @@ Notificación de prueba desde Gadgetbridge Bluetooth no está soportado. Bluetooth está desactivado. - pulsa el dispositivo conectado para el Gestor de App - pulsa un dispositivo para conectar No se puede conectar. ¿Dirección BT incorrecta? Gadgetbridge funcionando instalando binario %1$d/%2$d @@ -101,13 +99,11 @@ No se han proporcionado datos de usuario válidos, se usarán datos de usuario por defecto. Cuando tu MiBand vibre y parpadee, púlsala repetidas veces. Instalar - Haz visible tu dispositivo. No es probable que se detecten los dispositivos que ya están conectados. Nota: Imagen del dispositivo Nombre/Apodo Número de vibraciones Monitor de sueño - Guardar logs iniciando Recuperando datos de actividad Desde %1$s a %2$s @@ -178,9 +174,9 @@ Pasos Actividad Pasos hoy, objetivo: %1$s + No confirmar transferencia Si los datos no son marcados como descargados, no serán borrados de tu MiBand. Útil si Gadgetbridge se usa conjuntamente con otras apps. Mantendrá los datos de actividad en la MiBand incluso después de la sincronización. Útil si GB se usa junto con otras apps. - No confirmar transferencia Historial de pasos Pasos/min actuales Pasos totales diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4324f1f6..0ae83aa0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -178,9 +178,9 @@ Pas Activité en direct Nombre de pas aujourd\'hui, objectif: %1$s + Ne pas confirmer le transfert de données d\'activités Les données d\'activités ne seront pas effacées du bracelet si elles ne sont pas confirmées. Utile si GB est utilisé avec d\'autres applications. Les données d\'activités seront conservées sur le Mi Band après la synchronisation. Utile si GB est utilisé avec d\'autres applications. - Ne pas confirmer le transfert de données d\'activités Historique de pas Pas/minute actuel Nombre total de pas diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 14f87afb..3f1e3135 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -70,8 +70,6 @@ Notifica di prova creata da Gadgetbridge Bluetooth non supportato. Bluetooth disabilitato. - tocca il dispositivo connesso per gestire le App - tocca il dispositivo da connettere Impossibile connettersi. Indirizzo BT non valido? Gadgetbridge in esecuzione installazione del binario %1$d/%2$d @@ -102,13 +100,11 @@ Dati dell\'utente non inseriti, vengono usati dati d\'esempio. Quando la Mi Band vibra e lampeggia, dalle qualche leggero colpetto. Installa - Imposta il tuo dispositivo perchè sia rilevabile. I dispositivi attualmente connessi non saranno probabilmente rilevati. Nota: Immagine dispositivo Nome / Soprannome Numero vibrazioni Monitoraggio del sonno - Salva il log su file inizializzazione in corso Recupero dati attività Da %1$s a %2$s @@ -179,9 +175,9 @@ Passi Attività in tempo reale Passi di oggi, traguardo: %1$s + Non confermare il trasferimento dati Se il trasferimento non viene confermato, i dati rimangono memorizzati sulla Mi Band. Utile se GB è usato insieme ad altre app. Conserva i dati delle attività sulla Mi Band anche dopo averli sincronizzati. Utile se GB è usato insieme ad altre app. - Non confermare il trasferimento dati Storico dei passi Passi/minuto Passi totali diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 864ce541..f4013879 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -14,6 +14,7 @@ アプリ管理画面 削除 + キャッシュから削除 ステータス通知ブラックリスト @@ -44,6 +45,8 @@ インテント経由でPebbleに通知を送信するアプリケーションをサポートします。Conversationsに使用することができます。 一般ステータス通知対応 … スクリーンがオンのときにも + サイレント + サイレントモードに基づいて、送信される不要な通知を停止します。 いつも スクリーンがオフのとき なし diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index ddce42bc..2afb4a02 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -181,9 +181,9 @@ 걸음 수 실시간 활동 오늘 걸음 수, 목표: %1$s + 활동 데이터 전송을 확인하지 않음 만약 활동 데이터가 밴드에 확인되지 않았다면, 지워지지 않을 것입니다. 가젯브릿지가 다른 앱들과 같이 사용될 때 유용합니다. 동기화 이후에도 Mi Band의 활동 데이터가 유지될 것입니다. 가젯브릿지가 다른 앱들과 같이 사용될 때 유용합니다. - 활동 데이터 전송을 확인하지 않음 걸음 수 기록 현재의 분당 걸음 수 전체 걸음 수 @@ -224,4 +224,5 @@ 펌웨어가 전송되지 않음 심박수 심박수 + 자동으로 재연결 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f1748748..bc0929a3 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -67,8 +67,6 @@ To jest testowe powiadomienie z Gadgetbridge Bluetooth nie jest obsługiwane Bluetooth jest wyłączone - Kliknij podłączone urządzenia dla zarządzania aplikacjami - kliknij urządzenie by połączyć Nie można połączyć. Adres BT nieprawidłowy? Gadgetbridge działa Instalowanie binarki %1$d/%2$d @@ -99,13 +97,11 @@ Brak prawidłowych danych użytkownika, używam danych zastępczych na ten moment. Gdy twój Mi Band wibruje i błyska, stuknij go kilka razy pod rząd. instaluj - Uwidocznij swoje urządzenie. Aktualnie połączone urządzenia prawdopodobnie nie będą znalezione. Uwaga Obraz urządzenia Nazwisko/Pseudonim Liczba wibracji Monitor snu - Zapisuj logi Uruchamianie Pobieranie danych aktywności Od %1$s do %2$s @@ -176,9 +172,9 @@ Kroki Ostatnia aktywność Kroków dziś, cel: %1$s + Nie wysyłaj danych aktywności Gdy dane aktywności nie są przesłane na opaskę, wtedy nie będą usuwane. Przydatne gdy Gadgetbridge jest używany wraz z innymi aplikacjami Dane aktywności będą zachowane na Mi Band nawet po synchronizacji. Przydatne gdy Gadgetbridge jest używany z innymi aplikacjami. - Nie wysyłaj danych aktywności Historia kroków Aktualnie kroków/min Kroków łącznie diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5039e880..5ea805eb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -68,8 +68,6 @@ Это тестовое уведомление от Gadgetbridge Bluetooth не поддерживается. Bluetooth отключён. - нажмите на подключённое устройство для App Manager - нажмите на устройство для соединения Не удалось соединиться. Неверен адрес BT? Gadgetbridge запущен установки бинарного файла %1$d/%2$d @@ -100,13 +98,11 @@ Не предоставлено действительных данных пользователя. Используются данные по-умолчанию. Когда ваш Mi Band вибрирует и мигает, постучите по нему несколько раз. Установить - Подключённые в настоящее время устройства, скорее всего, не будут обнаружены. Заметка: Изображение устройства Имя/псевдоним Количество вибраций Анализ сна - Записывать файлы журнала Инициализация Получение данных активности От %1$s до %2$s @@ -177,9 +173,9 @@ Шаги Жизненная активность Шагов сегодня, цель: %1$s + Не передавать данные об активности Если данные об активности не будут переданы на устройство, оно не будет очищено. Полезно, если GB используется с другими приложениями. Хранить данные о деятельности на Mi Band, даже после синхронизации. Полезно, если Mi Band используется совместно с другими приложениями. - Не передавать данные об активности История шагов Текущие шаги в минуту Всего шагов diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f0d6f36a..7f79179f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -31,6 +31,9 @@ Дата і час Синхронізувати час під час з\'єднання Синхронізувати час під час з\'єднання з пристроєм, а також під час зміни часу чи часової зони в системі + Тема + Світла + Темна Сповіщення Повтори Виклики @@ -67,8 +70,6 @@ Це тестове сповіщення від Gadgetbridge Bluetooth не підтримується. Bluetooth вимкнуто. - натисніть на під\'єднаний пристрій для App Manager - натисніть на пристрій для з\'єднання Не вдалося з\'єднатися. Можливо помилкова адреса BT? Gadgetbridge запущено встановлення бінарного файлу %1$d/%2$d @@ -99,13 +100,11 @@ Не отримано дійсних даних користувача. Використовуються типові дані. Коли ваш Mi—Band вібрує та блимає, постукайте по ньому кілька раз. Встановити - Під\'єднані на даний момент пристрої, скоріш за все не будуть виявлені. Замітка: Зображення пристрою Ім\'я/нік Кількість вібрацій Аналіз сну - Записувати файли звіту Ініціалізація… Отримання даних активності Від %1$s до %2$s @@ -176,9 +175,9 @@ Кроки Життєва активність Кроків сьогодні, мета: %1$s + Не передавати дані про активність Якщо дані не будуть передані на пристрій, пристрій не буде очищений. Корисно, якщо Gadgetbridge використовується разом з іншими додатками. Дозволяє лишити дані на Mi-браслеті після синхронізації. Зазвичай використовується, якщо GB працює ще з іншими додатками. - Не передавати дані про активність Історія кроків Поточні кроки/хв Загалом кроків diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index af3de63c..f41f8478 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -52,8 +52,6 @@ Đây là một kiểm tra thông báo từ Gadgetbridge Không hỗ trợ Bluetooth. Đã tắt Bluetooth. - chạm vào thiết bị đã kết nối để chạy Trình quản lý ứng dụng - chạm vào một thiết bị để kết nối Không thể kết nối. Địa chỉ BT không hợp lệ? Gadgetbridge đang chạy đang cài phần mềm chạy %1$d/%2$d @@ -83,7 +81,6 @@ Ảnh thiết bị Tên/Bí danh Trình giám sát giấc ngủ - Ghi tập tin nhật ký đang khởi chạy Từ %1$s đến %2$s Đeo bên trái hay phải? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 133216e5..141484d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ App Manager Delete + Delete and remove from cache Notification Blacklist @@ -52,6 +53,8 @@ Support for applications which send Notifications to the Pebble via Intent. Can be used for Conversations. Generic notification support … also when screen is on + Do Not Disturb + Stop unwanted Notifications from being sent based on the Do Not Disturb mode. always when screen is off @@ -249,5 +252,4 @@ Heart Rate Heart Rate Reconnect automatically - diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 425e5c4a..abe77eba 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,13 @@ + + Pebble: fix more reconnnect issues + Pebble: fix deep sleep not being detected with Firmware 3.12 when using Pebble Health + Pebble: option in AppManager to delete files from cache + Pebble: enable pbw cache and watchface configuration for Firmware 2.x + Pebble: allow enabling of Pebble Health without "untested features" being enabled + Honour "Do Not Disturb" for phone calls and SMS + Pebble: hopefully fix some reconnect issues Mi Band: fix live activity monitoring running forever if back button pressed diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 4def8d01..6ae2be49 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -77,6 +77,13 @@ android:defaultValue="false" android:key="notifications_generic_whenscreenon" android:title="@string/pref_title_whenscreenon" /> + + +