diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e12b00cc..24d57af0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,38 @@ ###Changelog -####Version next -* Miband: allow the transfer of activity data without clearing MiBand's memory +####Version 0.6.1 +* Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch +* Pebble: Detect all known Pebble Versions including new "chalk" platform (Pebble Time Round) +* Option to ignore phone calls (useful for Pebble Dialer) +* Mi Band: Added progressbar for activity data transfer and fixes for firmware transfer progressbar +* Bugfix for app blacklist (some checkboxes where wrongly drawn as checked) + +####Version 0.6.0 +* Pebble: WIP implementantion of PebbleKit Intents to make some 3rd party Android apps work with the Pebble (eg. Ventoo) +* Pebble: Option to set reconnection attempts in settings (one attempt usually takes about 5 seconds) +* Support contolling all audio players that react to media buttons (can be chosen in settings) +* Treat SMS as generic notification if set to "never" (can be blacklisted there also if desired) +* Treat Conversations messagess as chat messages, even if arrived via Pebble Intents (nice icon for Pebble FW 3.x) +* Allow opening firmware / app files from the download manager "app" (technically a content provider) +* Mi Band: whitelisted a few firmware versions + +####Version 0.5.4 +* Mi Band: allow the transfer of activity data without clearing MiBand's memory +* Pebble: for generic notifications use generic icon instead of SMS icons on FW 3.x (thanks @roidelapluie) +* Pebble: use different icons and background colors for specific groups of applications (chat, mail, etc) (thanks @roidelapluie) +* In settings, support blacklisting apps for generic notifications ####Version 0.5.3 -* Pebble: For generic notifications, support dismissing individual notficications and "Open on Phone" feature (OG & PT) -* Pebble: Allow to treat K9 notifcations as generic notifications (if notification mode is set to never) -* Ignore QKSMS notificaions to avoid double notification for incoming SMS +* Pebble: For generic notifications, support dismissing individual notifications and "Open on Phone" feature (OG & PT) +* Pebble: Allow to treat K9 notifications as generic notifications (if notification mode is set to never) +* Ignore QKSMS notifications to avoid double notification for incoming SMS * Improved UI of Firmware/App installer * Device state again visible on lockscreen * Date display and navigation now working properly for all charts ####Version 0.5.2 * Pebble: support "dismiss all" action also on Pebble Time/FW 3.x notifications -* Miband: show a notification when the battery is below 10% +* Mi Band: show a notification when the battery is below 10% * Graphs are now using the same theme as the rest of the application * Graphs now show when the device was not worn by the user (for devices that send this information) * Remove unused settings option in charts view @@ -24,12 +43,12 @@ * Fix broken "find lost device" which was broken in 0.5.0 ####Version 0.5.0 -* Mi Band: fix setting wear location +* Mi Band: fix setting wear location * Pebble: experimental watchapp installation support for FW 3.x/Pebble Time * Pebble: support Pebble emulator via TCP connection (needs rebuild with INTERNET permission) * Pebble: use SMS/EMAIL icons for FW 3.x/Pebble Time * Pebble: do not throttle notifications -* Support going forward/backwards in time in the activy charts +* Support going forward/backwards in time in the activity charts * Various small bugfixes to the App/Fw Installation Activity ####Version 0.4.6 @@ -66,7 +85,7 @@ ####Version 0.4.2 * Material style for Lollipop -* Support for finding a lost device (vibrate until cacelled) +* Support for finding a lost device (vibrate until cancelled) * Mi Band: Support for vibration profiles, configurable for notifications * Pebble: Support taking screenshots from the device context menu (Pebble Time not supported yet) @@ -74,7 +93,7 @@ * New icons, thanks xphnx! * Improvements to Sleep Monitor charts * Pebble: use new Sleep Monitor for Morpheuz (previously Mi Band only) -* Pebble: expermimental support for FW 3.x notification protocol +* Pebble: experimental support for FW 3.x notification protocol * Pebble: dev option to force latest notification protocol ####Version 0.4.0 @@ -82,13 +101,13 @@ * Pebble: Support launching of watchapps though the AppManager Activity * Pebble: Support CM 12.1 default music app (Eleven) * Pebble: Fix firmware installation when all 8 app slots are in use -* Pebble: Fix firmware installation when Pebble is in recovery mode +* Pebble: Fix firmware installation when Pebble is in recovery mode * Pebble: Fix error when reinstalling apps, useful for upgrading/downgrading * Mi Band: Make vibration count configurable for different kinds of Notifications * Mi Band: Initial support for fetching activity data * Support rebooting Mi Band/Pebble through the Debug Activity * Add highly experimental sleep monitor (accessible via long press on a device) -* Fix Debug activity (SMS and E-Mail buttons were broken) +* Fix Debug activity (SMS and E-Mail buttons were broken) * Add Turkish translation contributed by Tarik Sekmen ####Version 0.3.5 @@ -100,7 +119,7 @@ ####Version 0.3.4 * Pebble: Huge speedup for app/firmware installation. * Pebble: Use a separate notification with progress bar for installation procedure -* Pebble: Bugfix for beeing stuck while waiting for a slot, when none is available +* Pebble: Bugfix for being stuck while waiting for a slot, when none is available * Mi Band: Display connection status in notification (previously Pebble only) ####Version 0.3.3 @@ -115,7 +134,7 @@ * Pebble: Check if firmware is compatible before allowing installation ####Version 0.3.1 -* Mi Band: Fix for notifications only woking in Debug +* Mi Band: Fix for notifications only working in Debug ####Version 0.3.0 * Mi Band: Initial support (see README.md) @@ -129,7 +148,7 @@ * Experimental pbw installation support (watchfaces/apps) * New icons for device and app lists * Fix for device list not refreshing when bluetooth gets turned on -* Filter out annyoing low battery notifications +* Filter out annoying low battery notifications * Fix for crash on some devices when creating a debug notification * Lots of internal changes preparing multi device support @@ -137,16 +156,16 @@ * Fix for DST (summer time) * Option to sync time on connect (enabled by default) * Opening .pbw files with Gadgetbridge prints some package information - (This was not meant to be released yet, but the DST fix made a new release neccessary) + (This was not meant to be released yet, but the DST fix made a new release necessary) ####Version 0.1.4 * New AppManager shows installed Apps/Watchfaces (removal possible via context menu) * Allow back navigation in ActionBar (Debug and AppMananger Activities) * Make sure Intent broadcasts do not leave Gadgetbridge -* Show hint in the Main Activiy (tap to connect etc) +* Show hint in the Main Activity (tap to connect etc) ####Version 0.1.3 -* Remove the connect button, list all suported devices and connect on tap instead +* Remove the connect button, list all supported devices and connect on tap instead * Display connection status and firmware of connected devices in the device list * Remove quit button from the service notification, put a quit item in the context menu instead @@ -157,7 +176,7 @@ ####Version 0.1.1 * Fixed various bugs regarding K-9 Mail notifications. -* "Generic notification support" in Setting now opens Androids "Notifcaion access" dialog. +* "Generic notification support" in Setting now opens Androids "Notification access" dialog. ####Version 0.1.0 * Initial release diff --git a/README.md b/README.md index 0c42e1ebb..cd11f8103 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,12 @@ need to create an account and transmit any of your data to the vendor's servers. * Support for generic notifications (above filtered out) * Dismiss individial notifications or open corresponding app on phone from the action menu (generic notifications) * Dismiss all notifications from the action menu (non-generic notifications) -* Music playback info (artist, album, track). Apollo and CM 12.1 Music App supported. +* Music playback info (artist, album, track) * Music control: play/pause, next track, previous track, volume up, volume down * List and remove installed apps/watchfaces -* Install .pbw files -* Install firmware from .pbz files +* Install watchfaces and firmware files (.pbw and .pbz) * Take and share screenshots from the Pebble's screen +* PebbleKit support for 3rd Party Android Apps support (experimental). * Morpheuz sleep data syncronization (experimental) ## Notes about the Pebble Time diff --git a/app/build.gradle b/app/build.gradle index 2a53d1da2..da8eb113b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "nodomain.freeyourgadget.gadgetbridge" minSdkVersion 19 targetSdkVersion 23 - versionCode 24 - versionName "0.5.3" + versionCode 27 + versionName "0.6.1" } buildTypes { release { @@ -46,7 +46,7 @@ dependencies { compile 'com.android.support:support-v4:23.0.0' compile 'com.github.tony19:logback-android-classic:1.1.1-3' compile 'org.slf4j:slf4j-api:1.7.7' - compile 'com.github.PhilJay:MPAndroidChart:2.1.0' + compile 'com.github.PhilJay:MPAndroidChart:v2.1.4' compile 'com.github.pfichtner:durationformatter:0.1.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e49194d18..d5b8053cd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,6 +66,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activities.ControlCenter" /> + + + @@ -164,6 +171,14 @@ + + + + + + + + - - + = Build.VERSION_CODES.LOLLIPOP; } + + public static HashSet blacklist = null; + + public static void loadBlackList() { + blacklist = (HashSet) sharedPrefs.getStringSet("package_blacklist", null); + if (blacklist == null) { + blacklist = new HashSet<>(); + } + } + + public static void saveBlackList() { + SharedPreferences.Editor editor = sharedPrefs.edit(); + if (blacklist.isEmpty()) { + editor.putStringSet("package_blacklist", null); + } else { + editor.putStringSet("package_blacklist", blacklist); + } + editor.apply(); + } + + public static void addToBlacklist(String packageName) { + if (!blacklist.contains(packageName)) { + blacklist.add(packageName); + saveBlackList(); + } + } + + public static synchronized void removeFromBlacklist(String packageName) { + blacklist.remove(packageName); + saveBlackList(); + } + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java index b5b16c84b..7327b5585 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBException.java @@ -4,12 +4,15 @@ public class GBException extends Exception { public GBException(String message, Throwable cause) { super(message, cause); } + public GBException(String message) { super(message); } + public GBException(Throwable cause) { super(cause); } + public GBException() { super(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java index 52da065ad..6a77ae4d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragment.java @@ -1,13 +1,14 @@ package nodomain.freeyourgadget.gadgetbridge.activities; -import android.support.v4.app.Fragment; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; /** * Abstract base class for fragments. Provides hooks that are called when * the fragment is made visible and invisible in the activity. also allows * the fragment to define the title to be shown in the activity. + * * @see AbstractGBFragmentActivity */ public abstract class AbstractGBFragment extends Fragment { @@ -25,6 +26,7 @@ public abstract class AbstractGBFragment extends Fragment { /** * Called when this fragment has been scrolled out of the activity. + * * @see #isVisibleInActivity() * @see #onMadeVisibleInActivity() */ @@ -54,6 +56,7 @@ public abstract class AbstractGBFragment extends Fragment { /** * Internal + * * @hide */ public void onMadeVisibleInActivityInternal() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java index 257a18048..b76e50c3a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractGBFragmentActivity.java @@ -1,20 +1,15 @@ package nodomain.freeyourgadget.gadgetbridge.activities; -import android.content.Context; import android.os.Bundle; -import android.os.PersistableBundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.PagerAdapter; -import android.util.AttributeSet; -import android.view.View; /** * A base activity that supports paging through fragments by swiping. * Subclasses will have to add a ViewPager to their layout and add something * like this to hook it to the fragments: - * + *

*

  * // Set up the ViewPager with the sections adapter.
  * ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
@@ -50,6 +45,7 @@ public abstract class AbstractGBFragmentActivity extends FragmentActivity {
     /**
      * Creates a PagerAdapter that will create the fragments to be used with this
      * activity. The fragments should typically extend AbstractGBFragment
+     *
      * @param fragmentManager
      * @return
      */
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java
index ee949e729..8866f8a72 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AlarmDetails.java
@@ -8,9 +8,9 @@ import android.view.MenuItem;
 import android.widget.CheckBox;
 import android.widget.TimePicker;
 
-import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
 import nodomain.freeyourgadget.gadgetbridge.GBApplication;
 import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
 
 public class AlarmDetails extends Activity {
 
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java
new file mode 100644
index 000000000..371572dba
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppBlacklistActivity.java
@@ -0,0 +1,122 @@
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v4.app.NavUtils;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+public class AppBlacklistActivity extends Activity {
+    private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(ControlCenter.ACTION_QUIT)) {
+                finish();
+            }
+        }
+    };
+
+    private SharedPreferences sharedPrefs;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_appblacklist);
+        getActionBar().setDisplayHomeAsUpEnabled(true);
+
+        final PackageManager pm = getPackageManager();
+        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
+        final List packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
+        ListView appListView = (ListView) findViewById(R.id.appListView);
+
+        final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item_with_checkbox, packageList) {
+            @Override
+            public View getView(int position, View view, ViewGroup parent) {
+                if (view == null) {
+                    LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                    view = inflater.inflate(R.layout.item_with_checkbox, parent, false);
+                }
+
+                ApplicationInfo appInfo = packageList.get(position);
+                TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details);
+                TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name);
+                ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
+                CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox);
+
+                deviceAppVersionAuthorLabel.setText(appInfo.packageName);
+                deviceAppNameLabel.setText(appInfo.loadLabel(pm));
+                deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
+
+                checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
+
+                return view;
+            }
+        };
+        appListView.setAdapter(adapter);
+
+        appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView parent, View v, int position, long id) {
+                String packageName = packageList.get(position).packageName;
+                CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
+                checkBox.toggle();
+                if (checkBox.isChecked()) {
+                    GBApplication.addToBlacklist(packageName);
+                } else {
+                    GBApplication.removeFromBlacklist(packageName);
+                }
+            }
+        });
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ControlCenter.ACTION_QUIT);
+
+        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                NavUtils.navigateUpFromSameTask(this);
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onDestroy() {
+        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+        super.onDestroy();
+    }
+}
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 94c8837f4..62a02ba64 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java
@@ -5,7 +5,9 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.SharedPreferences;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.support.v4.app.NavUtils;
 import android.support.v4.content.LocalBroadcastManager;
 import android.view.ContextMenu;
@@ -27,8 +29,6 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
 import nodomain.freeyourgadget.gadgetbridge.R;
 import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
 import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
-import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
-import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
 import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
 
 
@@ -54,14 +54,30 @@ public class AppManagerActivity extends Activity {
 
                     appList.add(new GBDeviceApp(uuid, appName, appCreator, "", appType));
                 }
+
+                if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
+                    appList.addAll(getSystemApps());
+                }
+
                 mGBDeviceAppAdapter.notifyDataSetChanged();
             }
         }
     };
-    final List appList = new ArrayList<>();
+
+    private SharedPreferences sharedPrefs;
+
+    private final List appList = new ArrayList<>();
     private GBDeviceAppAdapter mGBDeviceAppAdapter;
     private GBDeviceApp selectedApp = null;
 
+    private List getSystemApps() {
+        List systemApps = new ArrayList<>();
+        systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
+        systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.UNKNOWN));
+
+        return systemApps;
+    }
+
     private List getCachedApps() {
         List cachedAppList = new ArrayList<>();
         try {
@@ -84,6 +100,9 @@ public class AppManagerActivity extends Activity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
         setContentView(R.layout.activity_appmanager);
         getActionBar().setDisplayHomeAsUpEnabled(true);
 
@@ -95,15 +114,16 @@ public class AppManagerActivity extends Activity {
             @Override
             public void onItemClick(AdapterView parent, View v, int position, long id) {
                 UUID uuid = appList.get(position).getUUID();
-                GBApplication.deviceService().onAppStart(uuid);
+                GBApplication.deviceService().onAppStart(uuid, true);
             }
         });
 
         registerForContextMenu(appListView);
 
-        List cachedApps = getCachedApps();
-        for (GBDeviceApp app : cachedApps) {
-            appList.add(app);
+        appList.addAll(getCachedApps());
+
+        if (sharedPrefs.getBoolean("pebble_force_untested", false)) {
+            appList.addAll(getSystemApps());
         }
 
         IntentFilter filter = new IntentFilter();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java
index 9c263cf5e..ba7221b3f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ConfigureAlarms.java
@@ -12,10 +12,9 @@ import java.util.HashSet;
 import java.util.Set;
 
 import nodomain.freeyourgadget.gadgetbridge.GBApplication;
-import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
-import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
 import nodomain.freeyourgadget.gadgetbridge.R;
 import nodomain.freeyourgadget.gadgetbridge.adapter.GBAlarmListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
 
 import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
 
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
index 6a97fe90f..61eef3d36 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java
@@ -37,7 +37,6 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
 import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
 import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
 import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
-import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
 import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
 import nodomain.freeyourgadget.gadgetbridge.util.GB;
 
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
index 0372ba2e3..38aa18789 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
@@ -26,6 +26,8 @@ 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.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
 import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
 import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
 import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -70,17 +72,25 @@ public class DebugActivity extends Activity {
         sendSMSButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                GBApplication.deviceService().onSMS(getResources().getText(R.string.app_name).toString(), editContent.getText().toString());
+                NotificationSpec notificationSpec = new NotificationSpec();
+                notificationSpec.sender = getResources().getText(R.string.app_name).toString();
+                notificationSpec.body = editContent.getText().toString();
+                notificationSpec.type = NotificationType.SMS;
+                notificationSpec.id = -1;
+                GBApplication.deviceService().onNotification(notificationSpec);
             }
         });
         sendEmailButton = (Button) findViewById(R.id.sendEmailButton);
         sendEmailButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                GBApplication.deviceService().onEmail(
-                        getResources().getText(R.string.app_name).toString(),
-                        getResources().getText(R.string.test).toString(),
-                        editContent.getText().toString());
+                NotificationSpec notificationSpec = new NotificationSpec();
+                notificationSpec.sender = getResources().getText(R.string.app_name).toString();
+                notificationSpec.subject = editContent.getText().toString();
+                notificationSpec.body = editContent.getText().toString();
+                notificationSpec.type = NotificationType.EMAIL;
+                notificationSpec.id = -1;
+                GBApplication.deviceService().onNotification(notificationSpec);
             }
         });
 
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
index 0acc51797..53268827c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java
@@ -24,13 +24,13 @@ import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
 import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
 import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
 import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
 import nodomain.freeyourgadget.gadgetbridge.util.GB;
-import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.R;
-import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
 
 public class DiscoveryActivity extends Activity implements AdapterView.OnItemClickListener {
     private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
index a9ff065f3..74df6ecb2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
@@ -12,7 +12,6 @@ import android.support.v4.content.LocalBroadcastManager;
 import android.view.MenuItem;
 import android.view.View;
 import android.widget.Button;
-import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java
index c1a60e3c7..4c66c2173 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/InstallActivity.java
@@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
 
 public interface InstallActivity {
     void setInfoText(String text);
+
     void setInstallEnabled(boolean enable);
 
     void clearInstallItems();
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 735b68af7..a3dd4c67a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java
@@ -1,10 +1,15 @@
 package nodomain.freeyourgadget.gadgetbridge.activities;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.preference.ListPreference;
 import android.preference.Preference;
 import android.support.v4.content.LocalBroadcastManager;
 
+import java.util.List;
+
 import nodomain.freeyourgadget.gadgetbridge.R;
 import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
 
@@ -36,6 +41,15 @@ public class SettingsActivity extends AbstractSettingsActivity {
             }
         });
 
+        pref = findPreference("pref_key_blacklist");
+        pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            public boolean onPreferenceClick(Preference preference) {
+                Intent enableIntent = new Intent(SettingsActivity.this, AppBlacklistActivity.class);
+                startActivity(enableIntent);
+                return true;
+            }
+        });
+
         final Preference pebbleEmuAddr = findPreference("pebble_emu_addr");
         pebbleEmuAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
             @Override
@@ -59,13 +73,43 @@ public class SettingsActivity extends AbstractSettingsActivity {
             }
 
         });
+
+        // Get all receivers of Media Buttons
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+
+        PackageManager pm = getPackageManager();
+        List mediaReceivers = pm.queryBroadcastReceivers(mediaButtonIntent,
+                PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER);
+
+
+        CharSequence[] newEntries = new CharSequence[mediaReceivers.size() + 1];
+        CharSequence[] newValues = new CharSequence[mediaReceivers.size() + 1];
+        newEntries[0] = getString(R.string.pref_default);
+        newValues[0] = "default";
+
+        int i = 1;
+        for (ResolveInfo resolveInfo : mediaReceivers) {
+            newEntries[i] = resolveInfo.activityInfo.loadLabel(pm);
+            newValues[i] = resolveInfo.activityInfo.packageName;
+            i++;
+        }
+
+        final ListPreference audioPlayer = (ListPreference) findPreference("audio_player");
+        audioPlayer.setEntries(newEntries);
+        audioPlayer.setEntryValues(newValues);
+        audioPlayer.setDefaultValue(newValues[0]);
     }
 
     @Override
     protected String[] getPreferenceKeysWithSummary() {
         return new String[]{
+                "audio_player",
+                "notification_mode_calls",
+                "notification_mode_sms",
+                "notification_mode_k9mail",
                 "pebble_emu_addr",
-                "pebble_emu_port"
+                "pebble_emu_port",
+                "pebble_reconnect_attempts",
         };
     }
 
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
index bfe0e2f47..cbcda1162 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
@@ -376,7 +376,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
         String dateStringFrom = "";
         String dateStringTo = "";
 
-        LOG.info("number of samples:" + samples.size());
+        LOG.info("" + getTitle() + ": number of samples:" + samples.size());
         if (samples.size() > 1) {
             float movement_divisor;
             boolean annotate = true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
index d3a380e40..b60927b46 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
@@ -125,8 +125,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
         legendLabels.add(akDeepSleep.label);
         legendColors.add(akNotWorn.color);
         legendLabels.add(akNotWorn.label);
-        chart.getLegend().setColors(legendColors);
-        chart.getLegend().setLabels(legendLabels);
+        chart.getLegend().setCustom(legendColors, legendLabels);
     }
 
     @Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
index 20d166aaa..06c9758dd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsHost.java
@@ -14,10 +14,13 @@ public interface ChartsHost {
     GBDevice getDevice();
 
     void setStartDate(Date startDate);
+
     void setEndDate(Date endDate);
 
     Date getStartDate();
+
     Date getEndDate();
+
     void setDateInfo(String dateInfo);
 
     ViewGroup getDateBar();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java
deleted file mode 100644
index 34acacf4a..000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomBarChart.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package nodomain.freeyourgadget.gadgetbridge.activities.charts;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.github.mikephil.charting.charts.BarChart;
-
-/**
- * A BarChart with some specific customization, like
- * 
  • using a custom legend renderer that always uses fixed labels and colors
  • - */ -public class CustomBarChart extends BarChart { - - public CustomBarChart(Context context) { - super(context); - } - - public CustomBarChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CustomBarChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - mLegendRenderer = new CustomLegendRenderer(getViewPortHandler(), getLegend()); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomLegendRenderer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomLegendRenderer.java deleted file mode 100644 index b2308cdd4..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/CustomLegendRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.activities.charts; - -import android.graphics.Typeface; - -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.renderer.LegendRenderer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * A legend renderer that does *not* calculate the labels and colors automatically - * from the data sets or the data entries. - *

    - * Instead, they have to be provided manually, because otherwise the legend will - * be empty. - */ -public class CustomLegendRenderer extends LegendRenderer { - public CustomLegendRenderer(ViewPortHandler viewPortHandler, Legend legend) { - super(viewPortHandler, legend); - } - - @Override - public void computeLegend(ChartData data) { - if (!mLegend.isEnabled()) { - return; - } - - // don't call super to avoid computing colors and labels - // super.computeLegend(data); - - Typeface tf = mLegend.getTypeface(); - - if (tf != null) - mLegendLabelPaint.setTypeface(tf); - - mLegendLabelPaint.setTextSize(mLegend.getTextSize()); - mLegendLabelPaint.setColor(mLegend.getTextColor()); - - // calculate all dimensions of the mLegend - mLegend.calculateDimensions(mLegendLabelPaint); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java index 9c40dc7b4..6da15864b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java @@ -16,7 +16,8 @@ import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.PieData; import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.utils.ValueFormatter; +import com.github.mikephil.charting.formatter.ValueFormatter; +import com.github.mikephil.charting.utils.ViewPortHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +72,7 @@ public class SleepChartFragment extends AbstractChartFragment { PieDataSet set = new PieDataSet(entries, ""); set.setValueFormatter(new ValueFormatter() { @Override - public String getFormattedValue(float value) { + public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.SECONDS); } }); @@ -166,8 +167,7 @@ public class SleepChartFragment extends AbstractChartFragment { legendLabels.add(akLightSleep.label); legendColors.add(akDeepSleep.color); legendLabels.add(akDeepSleep.label); - chart.getLegend().setColors(legendColors); - chart.getLegend().setLabels(legendLabels); + chart.getLegend().setCustom(legendColors, legendLabels); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java index 552148fd6..c5f3daf7f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java @@ -133,6 +133,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment { GBDevice device = getChartsHost().getDevice(); if (device != null) { + // TODO: eek, this is device specific! mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress()); } @@ -195,8 +196,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment { List legendLabels = new ArrayList<>(1); legendColors.add(akActivity.color); legendLabels.add(getContext().getString(R.string.chart_steps)); - chart.getLegend().setColors(legendColors); - chart.getLegend().setLabels(legendLabels); + chart.getLegend().setCustom(legendColors, legendLabels); chart.getLegend().setTextColor(LEGEND_TEXT_COLOR); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java index f55cc896e..0e127cd8d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/DeviceCandidateAdapter.java @@ -10,10 +10,10 @@ import android.widget.TextView; import java.util.List; -import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public class DeviceCandidateAdapter extends ArrayAdapter { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java index 8e2df7665..adc071782 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBAlarmListAdapter.java @@ -15,9 +15,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Set; -import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; +import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java index 0e52d29d4..0cf8fe90d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java @@ -13,8 +13,8 @@ import android.widget.TextView; import java.util.List; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; public class GBDeviceAdapter extends ArrayAdapter { 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 901071c46..ed8086764 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java @@ -10,8 +10,8 @@ import android.widget.TextView; import java.util.List; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; public class GBDeviceAppAdapter extends ArrayAdapter { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java index 59ee6d077..e35c89350 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java @@ -12,13 +12,13 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; -import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript; -import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.util.GB; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME; import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY; @@ -179,9 +179,11 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl } ArrayList samples = new ArrayList(); final String where = "(provider=" + provider.getID() + " and timestamp>=" + timestamp_from + " and timestamp<=" + timestamp_to + getWhereClauseFor(activityTypes, provider) + ")"; + LOG.info("Activity query where: " + where); final String order = "timestamp"; try (SQLiteDatabase db = this.getReadableDatabase()) { try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) { + LOG.info("Activity query result: " + cursor.getCount() + " samples"); if (cursor.moveToFirst()) { do { GBActivitySample sample = new GBActivitySample( diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java index 5899d2e02..ed42b28db 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBAccess.java @@ -4,9 +4,9 @@ import android.content.Context; import android.os.AsyncTask; import android.widget.Toast; -import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public abstract class DBAccess extends AsyncTask { private final String mTask; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java index fb42e7ff2..7ea096341 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java @@ -6,8 +6,8 @@ import android.database.sqlite.SQLiteOpenHelper; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; public interface DBHandler { public SQLiteOpenHelper getHelper(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java new file mode 100644 index 000000000..79ee6b81f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java @@ -0,0 +1,9 @@ +package nodomain.freeyourgadget.gadgetbridge.deviceevents; + +import java.util.UUID; + +public class GBDeviceEventAppMessage extends GBDeviceEvent { + public UUID appUUID; + public int id; + public String message; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java index 010ff0af3..567782cda 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventNotificationControl.java @@ -9,6 +9,7 @@ public class GBDeviceEventNotificationControl extends GBDeviceEvent { UNKNOWN, DISMISS, DISMISS_ALL, - OPEN + OPEN, + MUTE } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index 07f146432..6d7bf8533 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -6,8 +6,9 @@ import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; /** * Specifies all events that GadgetBridge intends to send to the gadget device. @@ -15,11 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; * Implementations need to send/encode event to the connected device. */ public interface EventHandler { - void onSMS(String from, String body); - - void onEmail(String from, String subject, String body); - - void onGenericNotification(String title, String details, int handle); + void onNotification(NotificationSpec notificationSpec); void onSetTime(); @@ -35,7 +32,7 @@ public interface EventHandler { void onAppInfoReq(); - void onAppStart(UUID uuid); + void onAppStart(UUID uuid, boolean start); void onAppDelete(UUID uuid); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java index e293f23a0..d3c2bcc11 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/UnknownDeviceCoordinator.java @@ -4,11 +4,11 @@ import android.app.Activity; import android.content.Context; import android.net.Uri; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class UnknownDeviceCoordinator implements DeviceCoordinator { private final UnknownSampleProvider sampleProvider; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index eea1d0088..ef98120f2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -9,18 +9,16 @@ import android.preference.PreferenceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.util.Calendar; -import java.util.GregorianCalendar; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class MiBandCoordinator implements DeviceCoordinator { private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java index b356a207f..88a5e230c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandDateConverter.java @@ -4,28 +4,51 @@ import java.util.Calendar; import java.util.GregorianCalendar; public class MiBandDateConverter { + /** + * Creates a calendar object representing the current date and time. + */ + public static GregorianCalendar createCalendar() { + return new GregorianCalendar(); + } + /** * uses the standard algorithm to convert bytes received from the MiBand to a Calendar object + * * @param value * @return */ public static GregorianCalendar rawBytesToCalendar(byte[] value) { - GregorianCalendar timestamp = new GregorianCalendar(); - if (value.length == 6) { - timestamp.set(Calendar.YEAR, (2000 + value[0])); - timestamp.set(Calendar.MONTH, value[1]); - timestamp.set(Calendar.DATE, value[2]); - timestamp.set(Calendar.HOUR_OF_DAY, value[3]); - timestamp.set(Calendar.MINUTE, value[4]); - timestamp.set(Calendar.SECOND, value[5]); + return rawBytesToCalendar(value, 0); + } + return createCalendar(); + } + + /** + * uses the standard algorithm to convert bytes received from the MiBand to a Calendar object + * + * @param value + * @return + */ + public static GregorianCalendar rawBytesToCalendar(byte[] value, int offset) { + if (value.length - offset >= 6) { + GregorianCalendar timestamp = new GregorianCalendar( + value[offset] + 2000, + value[offset + 1], + value[offset + 2], + value[offset + 3], + value[offset + 4], + value[offset + 5]); + + return timestamp; } - return timestamp; + return createCalendar(); } /** * uses the standard algorithm to convert a Calendar object to a byte array to send to MiBand + * * @param timestamp * @return */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java index 2f96c7144..64a177dea 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandFWHelper.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Locale; -import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; public class MiBandFWHelper { @@ -32,6 +31,10 @@ public class MiBandFWHelper { 16779547, //1.0.9.27 tested by developer 16779568, //1.0.9.48 tested by developer 16779585, //1.0.9.65 tested by developer + 16779779, //1.0.10.3 reported on the wiki + 16779782, //1.0.10.6 reported on the wikiew + 16779787, //1.0.10.11 tested by developer + //16779790, //1.0.10.14 reported on the wiki (vibration does not work currently) }; public MiBandFWHelper(Uri uri, Context context) throws IOException { @@ -44,7 +47,7 @@ public class MiBandFWHelper { throw new IOException("Firmware has a filename that looks like a Pebble app/firmware."); } - try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))){ + try (InputStream in = new BufferedInputStream(cr.openInputStream(uri))) { this.fw = FileUtils.readAll(in, 1024 * 1024); // 1 MB if (fw.length <= firmwareVersionMajor || fw[firmwareVersionMajor] != 1) { throw new IOException("Firmware major version should be 1, probably this isn't a MiBand firmware."); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java index ba756e496..1049f9d8b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java @@ -13,12 +13,11 @@ import android.widget.TextView; import android.widget.Toast; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; +import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity; public class MiBandPairingActivity extends Activity { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java index fd16cf35a..99ff144da 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java @@ -5,9 +5,9 @@ import android.os.Bundle; import android.preference.Preference; import android.support.v4.content.LocalBroadcastManager; -import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_GENERIC; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL; @@ -15,8 +15,8 @@ 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_FITNESS_GOAL; 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_WEARSIDE; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_GENDER; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java index 804059912..a2fc1dc76 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandSampleProvider.java @@ -1,7 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.miband; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; public class MiBandSampleProvider implements SampleProvider { public static final byte TYPE_DEEP_SLEEP = 5; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java index 9486b3f2e..0ba3a5e99 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandService.java @@ -179,12 +179,13 @@ public class MiBandService { */ - /* MODES: unknown + /* MODES: probably related to the sample data structure + */ - public static final MODE_REGULAR_DATA_LEN_BYTE = 0x0t; + public static final byte MODE_REGULAR_DATA_LEN_BYTE = 0x0; - public static final MODE_REGULAR_DATA_LEN_MINITE = 0x1t - */ + // was MODE_REGULAR_DATA_LEN_MINITE + public static final byte MODE_REGULAR_DATA_LEN_MINUTE = 0x1; /* PROFILE: unknown diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/MorpheuzSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/MorpheuzSampleProvider.java index 6ec7d153c..e9bdf7a07 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/MorpheuzSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/MorpheuzSampleProvider.java @@ -1,7 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; public class MorpheuzSampleProvider implements SampleProvider { // raw types 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 7d2e17908..ababb0797 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 @@ -39,7 +39,17 @@ public class PBWInstallHandler implements InstallHandler { return; } - mPBWReader = new PBWReader(mUri, mContext, device.getHardwareVersion().equals("dvt") ? "basalt" : "aplite"); + String hwRev = device.getHardwareVersion(); + String platformName; + if (hwRev.startsWith("snowy")) { + platformName = "basalt"; + } else if (hwRev.startsWith("spalding")) { + platformName = "chalk"; + } else { + platformName = "aplite"; + } + + mPBWReader = new PBWReader(mUri, mContext, platformName); if (!mPBWReader.isValid()) { installActivity.setInfoText("pbw/pbz is broken or incompatible with your Hardware or Firmware."); installActivity.setInstallEnabled(false); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index b9f85a676..f5ac8c690 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -59,7 +59,7 @@ public class PBWReader { public PBWReader(Uri uri, Context context, String platform) { String platformDir = ""; - if (!uri.toString().endsWith(".pbz") && platform.equals("basalt")) { + if (!uri.toString().endsWith(".pbz") && !platform.equals("aplite")) { platformDir = platform + "/"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java new file mode 100644 index 000000000..4abc4dde1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleColor.java @@ -0,0 +1,72 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.pebble; + +public final class PebbleColor { + + public static final byte Black = (byte) 0b11000000; + public static final byte OxfordBlue = (byte) 0b11000001; + public static final byte DukeBlue = (byte) 0b11000010; + public static final byte Blue = (byte) 0b11000011; + public static final byte DarkGreen = (byte) 0b11000100; + public static final byte MidnightGreen = (byte) 0b11000101; + public static final byte CobaltBlue = (byte) 0b11000110; + public static final byte BlueMoon = (byte) 0b11000111; + public static final byte IslamicGreen = (byte) 0b11001000; + public static final byte JaegerGreen = (byte) 0b11001001; + public static final byte TiffanyBlue = (byte) 0b11001010; + public static final byte VividCerulean = (byte) 0b11001011; + public static final byte Green = (byte) 0b11001100; + public static final byte Malachite = (byte) 0b11001101; + public static final byte MediumSpringGreen = (byte) 0b11001110; + public static final byte Cyan = (byte) 0b11001111; + public static final byte BulgarianRose = (byte) 0b11010000; + public static final byte ImperialPurple = (byte) 0b11010001; + public static final byte Indigo = (byte) 0b11010010; + public static final byte ElectricUltramarine = (byte) 0b11010011; + public static final byte ArmyGreen = (byte) 0b11010100; + public static final byte DarkGray = (byte) 0b11010101; + public static final byte Liberty = (byte) 0b11010110; + public static final byte VeryLightBlue = (byte) 0b11010111; + public static final byte KellyGreen = (byte) 0b11011000; + public static final byte MayGreen = (byte) 0b11011001; + public static final byte CadetBlue = (byte) 0b11011010; + public static final byte PictonBlue = (byte) 0b11011011; + public static final byte BrightGreen = (byte) 0b11011100; + public static final byte ScreaminGreen = (byte) 0b11011101; + public static final byte MediumAquamarine = (byte) 0b11011110; + public static final byte ElectricBlue = (byte) 0b11011111; + public static final byte DarkCandyAppleRed = (byte) 0b11100000; + public static final byte JazzberryJam = (byte) 0b11100001; + public static final byte Purple = (byte) 0b11100010; + public static final byte VividViolet = (byte) 0b11100011; + public static final byte WindsorTan = (byte) 0b11100100; + public static final byte RoseVale = (byte) 0b11100101; + public static final byte Purpureus = (byte) 0b11100110; + public static final byte LavenderIndigo = (byte) 0b11100111; + public static final byte Limerick = (byte) 0b11101000; + public static final byte Brass = (byte) 0b11101001; + public static final byte LightGray = (byte) 0b11101010; + public static final byte BabyBlueEyes = (byte) 0b11101011; + public static final byte SpringBud = (byte) 0b11101100; + public static final byte Inchworm = (byte) 0b11101101; + public static final byte MintGreen = (byte) 0b11101110; + public static final byte Celeste = (byte) 0b11101111; + public static final byte Red = (byte) 0b11110000; + public static final byte Folly = (byte) 0b11110001; + public static final byte FashionMagenta = (byte) 0b11110010; + public static final byte Magenta = (byte) 0b11110011; + public static final byte Orange = (byte) 0b11110100; + public static final byte SunsetOrange = (byte) 0b11110101; + public static final byte BrilliantRose = (byte) 0b11110110; + public static final byte ShockingPink = (byte) 0b11110111; + public static final byte ChromeYellow = (byte) 0b11111000; + public static final byte Rajah = (byte) 0b11111001; + public static final byte Melon = (byte) 0b11111010; + public static final byte RichBrilliantLavender = (byte) 0b11111011; + public static final byte Yellow = (byte) 0b11111100; + public static final byte Icterine = (byte) 0b11111101; + public static final byte PastelYellow = (byte) 0b11111110; + public static final byte White = (byte) 0b11111111; + public static final byte Clear = (byte) 0b00000000; + +} + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java new file mode 100644 index 000000000..18b409ae0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleIconID.java @@ -0,0 +1,95 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.pebble; + +public final class PebbleIconID { + + public static final int NOTIFICATION_GENERIC = 1; + public static final int TIMELINE_MISSED_CALL = 2; + public static final int NOTIFICATION_REMINDER = 3; + public static final int NOTIFICATION_FLAG = 4; + public static final int NOTIFICATION_WHATSAPP = 5; + public static final int NOTIFICATION_TWITTER = 6; + public static final int NOTIFICATION_TELEGRAM = 7; + public static final int NOTIFICATION_GOOGLE_HANGOUTS = 8; + public static final int NOTIFICATION_GMAIL = 9; + public static final int NOTIFICATION_FACEBOOK_MESSENGER = 10; + public static final int NOTIFICATION_FACEBOOK = 11; + public static final int AUDIO_CASSETTE = 12; + public static final int ALARM_CLOCK = 13; + public static final int TIMELINE_WEATHER = 14; + public static final int TIMELINE_SUN = 16; + public static final int TIMELINE_SPORTS = 17; + public static final int GENERIC_EMAIL = 19; + public static final int AMERICAN_FOOTBALL = 20; + public static final int TIMELINE_CALENDAR = 21; + public static final int TIMELINE_BASEBALL = 22; + public static final int BIRTHDAY_EVENT = 23; + public static final int CAR_RENTAL = 24; + public static final int CLOUDY_DAY = 25; + public static final int CRICKET_GAME = 26; + public static final int DINNER_RESERVATION = 27; + public static final int GENERIC_WARNING = 28; + public static final int GLUCOSE_MONITOR = 29; + public static final int HOCKEY_GAME = 30; + public static final int HOTEL_RESERVATION = 31; + public static final int LIGHT_RAIN = 32; + public static final int LIGHT_SNOW = 33; + public static final int MOVIE_EVENT = 34; + public static final int MUSIC_EVENT = 35; + public static final int NEWS_EVENT = 36; + public static final int PARTLY_CLOUDY = 37; + public static final int PAY_BILL = 38; + public static final int RADIO_SHOW = 39; + public static final int SCHEDULED_EVENT = 40; + public static final int SOCCER_GAME = 41; + public static final int STOCKS_EVENT = 42; + public static final int RESULT_DELETED = 43; + public static final int CHECK_INTERNET_CONNECTION = 44; + public static final int GENERIC_SMS = 45; + public static final int RESULT_MUTE = 46; + public static final int RESULT_SENT = 47; + public static final int WATCH_DISCONNECTED = 48; + public static final int DURING_PHONE_CALL = 49; + public static final int TIDE_IS_HIGH = 50; + public static final int RESULT_DISMISSED = 51; + public static final int HEAVY_RAIN = 52; + public static final int HEAVY_SNOW = 53; + public static final int SCHEDULED_FLIGHT = 54; + public static final int GENERIC_CONFIRMATION = 55; + public static final int DAY_SEPARATOR = 56; + public static final int NO_EVENTS = 57; + public static final int NOTIFICATION_BLACKBERRY_MESSENGER = 58; + public static final int NOTIFICATION_INSTAGRAM = 59; + public static final int NOTIFICATION_MAILBOX = 60; + public static final int NOTIFICATION_GOOGLE_INBOX = 61; + public static final int RESULT_FAILED = 62; + public static final int GENERIC_QUESTION = 63; + public static final int NOTIFICATION_OUTLOOK = 64; + public static final int RAINING_AND_SNOWING = 65; + public static final int REACHED_FITNESS_GOAL = 66; + public static final int NOTIFICATION_LINE = 67; + public static final int NOTIFICATION_SKYPE = 68; + public static final int NOTIFICATION_SNAPCHAT = 69; + public static final int NOTIFICATION_VIBER = 70; + public static final int NOTIFICATION_WECHAT = 71; + public static final int NOTIFICATION_YAHOO_MAIL = 72; + public static final int TV_SHOW = 73; + public static final int BASKETBALL = 74; + public static final int DISMISSED_PHONE_CALL = 75; + public static final int NOTIFICATION_GOOGLE_MESSENGER = 76; + public static final int NOTIFICATION_HIPCHAT = 77; + public static final int INCOMING_PHONE_CALL = 78; + public static final int NOTIFICATION_KAKAOTALK = 79; + public static final int NOTIFICATION_KIK = 80; + public static final int NOTIFICATION_LIGHTHOUSE = 81; + public static final int LOCATION = 82; + public static final int SETTINGS = 83; + public static final int SUNRISE = 84; + public static final int SUNSET = 85; + public static final int FACETIME_DISMISSED = 86; + public static final int FACETIME_INCOMING = 87; + public static final int FACETIME_OUTGOING = 88; + public static final int FACETIME_MISSED = 89; + public static final int FACETIME_DURING = 90; + public static final int BLUESCREEN_OF_DEATH = 91; + public static final int START_MUSIC_PHONE = 92; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java index b68eb5617..22d06fd4f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java @@ -10,7 +10,6 @@ import android.support.v4.content.LocalBroadcastManager; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; -import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; public class BluetoothStateChangeReceiver extends BroadcastReceiver { @Override 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 519020f0b..c8bf1cd98 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java @@ -13,6 +13,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; public class K9Receiver extends BroadcastReceiver { @@ -42,9 +44,9 @@ public class K9Receiver extends BroadcastReceiver { "uri" }; - String sender = ""; - String subject = ""; - String preview = ""; + NotificationSpec notificationSpec = new NotificationSpec(); + notificationSpec.id = -1; + notificationSpec.type = NotificationType.EMAIL; /* * there seems to be no way to specify the the uri in the where clause. @@ -57,14 +59,14 @@ public class K9Receiver extends BroadcastReceiver { do { String uri = c.getString(c.getColumnIndex("uri")); if (uri.equals(uriWanted)) { - sender = c.getString(c.getColumnIndex("senderAddress")); - subject = c.getString(c.getColumnIndex("subject")); - preview = c.getString(c.getColumnIndex("preview")); + notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress")); + notificationSpec.subject = c.getString(c.getColumnIndex("subject")); + notificationSpec.body = c.getString(c.getColumnIndex("preview")); break; } } while (c.moveToNext()); c.close(); - GBApplication.deviceService().onEmail(sender, subject, preview); + GBApplication.deviceService().onNotification(notificationSpec); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index 45ce6cda3..371e33d44 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -12,6 +12,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; public class MusicPlaybackReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); + private static String mLastSource; + @Override public void onReceive(Context context, Intent intent) { String artist = intent.getStringExtra("artist"); 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 016f93d29..11a88c191 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -9,6 +9,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.PowerManager; import android.preference.PreferenceManager; @@ -20,6 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; public class NotificationListener extends NotificationListenerService { @@ -32,45 +36,60 @@ public class NotificationListener extends NotificationListenerService { = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss_all"; public static final String ACTION_OPEN = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open"; + public static final String ACTION_MUTE + = "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute"; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @SuppressLint("NewApi") @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(ACTION_OPEN)) { - StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); - int handle = intent.getIntExtra("handle", -1); - for (StatusBarNotification sbn : sbns) { - if ((int) sbn.getPostTime() == handle) { - try { - PendingIntent pi = sbn.getNotification().contentIntent; - if (pi != null) { - pi.send(); + switch (action) { + case ACTION_MUTE: + case ACTION_OPEN: { + StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); + int handle = intent.getIntExtra("handle", -1); + for (StatusBarNotification sbn : sbns) { + if ((int) sbn.getPostTime() == handle) { + if (action.equals(ACTION_OPEN)) { + try { + PendingIntent pi = sbn.getNotification().contentIntent; + if (pi != null) { + pi.send(); + } + } catch (PendingIntent.CanceledException e) { + e.printStackTrace(); + } + } else { + // ACTION_MUTE + LOG.info("going to mute " + sbn.getPackageName()); + GBApplication.addToBlacklist(sbn.getPackageName()); } - } catch (PendingIntent.CanceledException e) { - e.printStackTrace(); } } + break; } - } else if (action.equals(ACTION_DISMISS)) { - StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); - int handle = intent.getIntExtra("handle", -1); - for (StatusBarNotification sbn : sbns) { - if ((int) sbn.getPostTime() == handle) { - if (GBApplication.isRunningLollipopOrLater()) { - String key = sbn.getKey(); - NotificationListener.this.cancelNotification(key); - } else { - int id = sbn.getId(); - String pkg = sbn.getPackageName(); - String tag = sbn.getTag(); - NotificationListener.this.cancelNotification(pkg, tag, id); + case ACTION_DISMISS: { + StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications(); + int handle = intent.getIntExtra("handle", -1); + for (StatusBarNotification sbn : sbns) { + if ((int) sbn.getPostTime() == handle) { + if (GBApplication.isRunningLollipopOrLater()) { + String key = sbn.getKey(); + NotificationListener.this.cancelNotification(key); + } else { + int id = sbn.getId(); + String pkg = sbn.getPackageName(); + String tag = sbn.getTag(); + NotificationListener.this.cancelNotification(pkg, tag, id); + } } } + break; } - } else if (action.equals(ACTION_DISMISS_ALL)) { - NotificationListener.this.cancelAllNotifications(); + case ACTION_DISMISS_ALL: + NotificationListener.this.cancelAllNotifications(); + break; } } @@ -83,6 +102,7 @@ public class NotificationListener extends NotificationListenerService { filterLocal.addAction(ACTION_OPEN); filterLocal.addAction(ACTION_DISMISS); filterLocal.addAction(ACTION_DISMISS_ALL); + filterLocal.addAction(ACTION_MUTE); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal); } @@ -128,8 +148,6 @@ public class NotificationListener extends NotificationListenerService { if (source.equals("android") || source.equals("com.android.systemui") || source.equals("com.android.dialer") || - source.equals("com.android.mms") || - source.equals("com.moez.QKSMS") || source.equals("com.cyanogenmod.eleven")) { return; } @@ -146,21 +164,69 @@ public class NotificationListener extends NotificationListenerService { } } - LOG.info("Processing notification from source " + source); - - Bundle extras = notification.extras; - String title = extras.getCharSequence(Notification.EXTRA_TITLE).toString(); - String content = null; - if (extras.containsKey(Notification.EXTRA_TEXT)) { - CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT); - if (contentCS != null) { - content = contentCS.toString(); + if (source.equals("com.moez.QKSMS") || source.equals("com.android.mms")) { + if (!"never".equals(sharedPrefs.getString("notification_mode_sms", "when_screen_off"))) { + return; } } - if (content != null) { - GBApplication.deviceService().onGenericNotification(title, content, (int) sbn.getPostTime()); //FIMXE: a truly unique id would be better + if (GBApplication.blacklist != null && GBApplication.blacklist.contains(source)) { + return; } + + NotificationSpec notificationSpec = new NotificationSpec(); + + // determinate Source App Name ("Label") + PackageManager pm = getPackageManager(); + ApplicationInfo ai = null; + try { + ai = pm.getApplicationInfo(source, 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + if (ai != null) { + notificationSpec.sourceName = (String) pm.getApplicationLabel(ai); + } + + switch (source) { + case "org.mariotaku.twidere": + case "com.twitter.android": + case "org.andstatus.app": + case "org.mustard.android": + notificationSpec.type = NotificationType.TWITTER; + break; + case "com.fsck.k9": + case "com.android.email": + notificationSpec.type = NotificationType.EMAIL; + break; + case "com.moez.QKSMS": + case "com.android.mms": + notificationSpec.type = NotificationType.SMS; + break; + case "eu.siacs.conversations": + notificationSpec.type = NotificationType.CHAT; + break; + case "org.indywidualni.fblite": + notificationSpec.type = NotificationType.FACEBOOK; + break; + default: + notificationSpec.type = NotificationType.UNDEFINED; + break; + } + + LOG.info("Processing notification from source " + source); + + Bundle extras = notification.extras; + notificationSpec.title = extras.getCharSequence(Notification.EXTRA_TITLE).toString(); + if (extras.containsKey(Notification.EXTRA_TEXT)) { + CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT); + if (contentCS != null) { + notificationSpec.body = contentCS.toString(); + } + } + + notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better + GBApplication.deviceService().onNotification(notificationSpec); } private boolean isServiceRunning() { @@ -177,4 +243,4 @@ public class NotificationListener extends NotificationListenerService { public void onNotificationRemoved(StatusBarNotification sbn) { } -} \ No newline at end of file +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java index 4fa3bcba2..01dab88f6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PebbleReceiver.java @@ -13,6 +13,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; public class PebbleReceiver extends BroadcastReceiver { @@ -32,9 +34,6 @@ public class PebbleReceiver extends BroadcastReceiver { } } - String title; - String body; - String messageType = intent.getStringExtra("messageType"); if (!messageType.equals("PEBBLE_ALERT")) { LOG.info("non PEBBLE_ALERT message type not supported"); @@ -46,18 +45,26 @@ public class PebbleReceiver extends BroadcastReceiver { return; } + NotificationSpec notificationSpec = new NotificationSpec(); + notificationSpec.id = -1; + String notificationData = intent.getStringExtra("notificationData"); try { JSONArray notificationJSON = new JSONArray(notificationData); - title = notificationJSON.getJSONObject(0).getString("title"); - body = notificationJSON.getJSONObject(0).getString("body"); + notificationSpec.title = notificationJSON.getJSONObject(0).getString("title"); + notificationSpec.body = notificationJSON.getJSONObject(0).getString("body"); } catch (JSONException e) { e.printStackTrace(); return; } - if (title != null && body != null) { - GBApplication.deviceService().onSMS(title, body); + if (notificationSpec.title != null) { + notificationSpec.type = NotificationType.UNDEFINED; + String sender = intent.getStringExtra("sender"); + if ("Conversations".equals(sender)) { + notificationSpec.type = NotificationType.CHAT; + } + GBApplication.deviceService().onNotification(notificationSpec); } } } 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 f19a84d99..c17829176 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java @@ -3,6 +3,8 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import nodomain.freeyourgadget.gadgetbridge.GBApplication; @@ -22,11 +24,11 @@ public class PhoneCallReceiver extends BroadcastReceiver { String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE); String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER); int state = 0; - if (stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)) { + if (TelephonyManager.EXTRA_STATE_IDLE.equals(stateStr)) { state = TelephonyManager.CALL_STATE_IDLE; - } else if (stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) { + } else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(stateStr)) { state = TelephonyManager.CALL_STATE_OFFHOOK; - } else if (stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)) { + } else if (TelephonyManager.EXTRA_STATE_RINGING.equals(stateStr)) { state = TelephonyManager.CALL_STATE_RINGING; } @@ -62,6 +64,10 @@ public class PhoneCallReceiver extends BroadcastReceiver { break; } if (callCommand != null) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) { + return; + } GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand); } mLastState = state; 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 72cff9990..13e80c6cd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java @@ -10,6 +10,8 @@ import android.preference.PreferenceManager; import android.telephony.SmsMessage; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; public class SMSReceiver extends BroadcastReceiver { @@ -27,16 +29,20 @@ public class SMSReceiver extends BroadcastReceiver { } } + NotificationSpec notificationSpec = new NotificationSpec(); + notificationSpec.id = -1; + notificationSpec.type = NotificationType.SMS; + Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); for (Object pdu1 : pdus) { byte[] pdu = (byte[]) pdu1; SmsMessage message = SmsMessage.createFromPdu(pdu); - String body = message.getDisplayMessageBody(); - String sender = message.getOriginatingAddress(); - if (sender != null && body != null) { - GBApplication.deviceService().onSMS(sender, body); + notificationSpec.body = message.getDisplayMessageBody(); + notificationSpec.phoneNumber = message.getOriginatingAddress(); + if (notificationSpec.phoneNumber != null) { + GBApplication.deviceService().onNotification(notificationSpec); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBActivitySample.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBActivitySample.java index 861dc8a1b..1cb7416d6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBActivitySample.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBActivitySample.java @@ -16,6 +16,19 @@ public class GBActivitySample implements ActivitySample { this.intensity = intensity; this.steps = steps; this.type = type; + validate(); + } + + private void validate() { + if (steps < 0) { + throw new IllegalArgumentException("steps must be > 0"); + } + if (intensity < 0) { + throw new IllegalArgumentException("intensity must be > 0"); + } + if (timestamp < 0) { + throw new IllegalArgumentException("timestamp must be > 0"); + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 72458e62c..a0292e889 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -9,10 +9,10 @@ import android.support.v4.content.LocalBroadcastManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.model.BatteryState; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; public class GBDevice implements Parcelable { public static final String ACTION_DEVICE_CHANGED @@ -153,7 +153,7 @@ public class GBDevice implements Parcelable { if (mBusyTask != null) { LOG.warn("Attempt to mark device as busy with: " + task + ", but is already busy with: " + mBusyTask); } - LOG.info("Mark device as busy: " + mBusyTask); + LOG.info("Mark device as busy: " + task); mBusyTask = task; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java index 3d65a98c9..62f772976 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java @@ -4,9 +4,9 @@ import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.Parcelable; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; /** */ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 5fd1efb79..2782d6093 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -11,6 +11,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; @@ -79,28 +80,16 @@ public class GBDeviceService implements DeviceService { } @Override - public void onSMS(String from, String body) { - Intent intent = createIntent().setAction(ACTION_NOTIFICATION_SMS) - .putExtra(EXTRA_NOTIFICATION_SENDER, from) - .putExtra(EXTRA_NOTIFICATION_BODY, body); - invokeService(intent); - } - - @Override - public void onEmail(String from, String subject, String body) { - Intent intent = createIntent().setAction(ACTION_NOTIFICATION_EMAIL) - .putExtra(EXTRA_NOTIFICATION_SENDER, from) - .putExtra(EXTRA_NOTIFICATION_SUBJECT, subject) - .putExtra(EXTRA_NOTIFICATION_BODY, body); - invokeService(intent); - } - - @Override - public void onGenericNotification(String title, String details, int handle) { - Intent intent = createIntent().setAction(ACTION_NOTIFICATION_GENERIC) - .putExtra(EXTRA_NOTIFICATION_TITLE, title) - .putExtra(EXTRA_NOTIFICATION_BODY, details) - .putExtra(EXTRA_NOTIFICATION_HANDLE, handle); + public void onNotification(NotificationSpec notificationSpec) { + Intent intent = createIntent().setAction(ACTION_NOTIFICATION) + .putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber) + .putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender) + .putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject) + .putExtra(EXTRA_NOTIFICATION_TITLE, notificationSpec.title) + .putExtra(EXTRA_NOTIFICATION_BODY, notificationSpec.body) + .putExtra(EXTRA_NOTIFICATION_ID, notificationSpec.id) + .putExtra(EXTRA_NOTIFICATION_TYPE, notificationSpec.type) + .putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName); invokeService(intent); } @@ -148,9 +137,10 @@ public class GBDeviceService implements DeviceService { } @Override - public void onAppStart(UUID uuid) { + public void onAppStart(UUID uuid, boolean start) { Intent intent = createIntent().setAction(ACTION_STARTAPP) - .putExtra(EXTRA_APP_UUID, uuid); + .putExtra(EXTRA_APP_UUID, uuid) + .putExtra(EXTRA_APP_START, start); invokeService(intent); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java index 4507a8d71..7726be44b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityAmount.java @@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.model; import android.content.Context; -import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; - public class ActivityAmount { private int activityKind; private short percent; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 2833019b3..6a2c54d69 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -13,9 +13,8 @@ public interface DeviceService extends EventHandler { static final String ACTION_START = PREFIX + ".action.start"; static final String ACTION_CONNECT = PREFIX + ".action.connect"; - static final String ACTION_NOTIFICATION_GENERIC = PREFIX + ".action.notification_generic"; + static final String ACTION_NOTIFICATION = PREFIX + ".action.notification"; static final String ACTION_NOTIFICATION_SMS = PREFIX + ".action.notification_sms"; - static final String ACTION_NOTIFICATION_EMAIL = PREFIX + ".action.notification_email"; static final String ACTION_CALLSTATE = PREFIX + ".action.callstate"; static final String ACTION_SETTIME = PREFIX + ".action.settime"; static final String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo"; @@ -34,11 +33,14 @@ public interface DeviceService extends EventHandler { static final String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps"; static final String EXTRA_DEVICE_ADDRESS = "device_address"; - static final String EXTRA_NOTIFICATION_TITLE = "notification_title"; static final String EXTRA_NOTIFICATION_BODY = "notification_body"; + static final String EXTRA_NOTIFICATION_ID = "notification_id"; + static final String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber"; static final String EXTRA_NOTIFICATION_SENDER = "notification_sender"; + static final String EXTRA_NOTIFICATION_SOURCENAME = "notification_sourcename"; static final String EXTRA_NOTIFICATION_SUBJECT = "notification_subject"; - static final String EXTRA_NOTIFICATION_HANDLE = "notification_handle"; + static final String EXTRA_NOTIFICATION_TITLE = "notification_title"; + static final String EXTRA_NOTIFICATION_TYPE = "notification_type"; static final String EXTRA_FIND_START = "find_start"; static final String EXTRA_CALL_COMMAND = "call_command"; static final String EXTRA_CALL_PHONENUMBER = "call_phonenumber"; @@ -46,6 +48,7 @@ public interface DeviceService extends EventHandler { static final String EXTRA_MUSIC_ALBUM = "music_album"; static final String EXTRA_MUSIC_TRACK = "music_track"; static final String EXTRA_APP_UUID = "app_uuid"; + static final String EXTRA_APP_START = "app_start"; static final String EXTRA_URI = "uri"; static final String EXTRA_ALARMS = "alarms"; static final String EXTRA_PERFORM_PAIR = "perform_pair"; @@ -53,15 +56,18 @@ public interface DeviceService extends EventHandler { static final String EXTRA_REALTIME_STEPS = "realtime_steps"; static final String EXTRA_TIMESTAMP = "timestamp"; - void start(); void connect(); + void connect(@Nullable String deviceAddress); + void connect(@Nullable String deviceAddress, boolean performPair); + void disconnect(); void quit(); + /** * Requests information from the {@link DeviceCommunicationService} about the connection state, * firmware info, etc. diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java index 1fe0313df..dcb768831 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ItemWithDetails.java @@ -2,6 +2,8 @@ package nodomain.freeyourgadget.gadgetbridge.model; public interface ItemWithDetails { String getName(); + String getDetails(); + int getIcon(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java new file mode 100644 index 000000000..e29a30d85 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java @@ -0,0 +1,12 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public class NotificationSpec { + public int id; + public String sender; + public String phoneNumber; + public String title; + public String subject; + public String body; + public NotificationType type; + public String sourceName; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java new file mode 100644 index 000000000..63feca893 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java @@ -0,0 +1,12 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +public enum NotificationType { + + UNDEFINED, + + CHAT, + EMAIL, + FACEBOOK, + SMS, + TWITTER, +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java index da29ee76d..5cb945119 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java @@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.model; public enum ServiceCommand { - UNDEFINEND, + UNDEFINED, CALL_ACCEPT, CALL_END, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index 3bc4e64de..cc499efdc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -220,6 +220,9 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { case OPEN: action = NotificationListener.ACTION_OPEN; break; + case MUTE: + action = NotificationListener.ACTION_MUTE; + break; } if (action != null) { Intent notificationListenerIntent = new Intent(action); 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 4f4a2f1a4..ec98f6b0b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -26,7 +26,8 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -38,9 +39,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_EN import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_INSTALL; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_EMAIL; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_GENERIC; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION_SMS; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_NOTIFICATION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REBOOT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_APPINFO; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO; @@ -51,6 +50,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS; +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_CALL_COMMAND; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; @@ -61,10 +61,13 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUS import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_BODY; -import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_HANDLE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_ID; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; @@ -180,25 +183,20 @@ public class DeviceCommunicationService extends Service { case ACTION_REQUEST_DEVICEINFO: mGBDevice.sendDeviceUpdateIntent(this); break; - case ACTION_NOTIFICATION_GENERIC: { - String title = intent.getStringExtra(EXTRA_NOTIFICATION_TITLE); - String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); - int handle = intent.getIntExtra(EXTRA_NOTIFICATION_HANDLE,-1); - mDeviceSupport.onGenericNotification(title, body, handle); - break; - } - case ACTION_NOTIFICATION_SMS: { - String sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER); - String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); - String senderName = getContactDisplayNameByNumber(sender); - mDeviceSupport.onSMS(senderName, body); - break; - } - case ACTION_NOTIFICATION_EMAIL: { - String sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER); - String subject = intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT); - String body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); - mDeviceSupport.onEmail(sender, subject, body); + case ACTION_NOTIFICATION: { + NotificationSpec notificationSpec = new NotificationSpec(); + notificationSpec.phoneNumber = intent.getStringExtra(EXTRA_NOTIFICATION_PHONENUMBER); + notificationSpec.sender = intent.getStringExtra(EXTRA_NOTIFICATION_SENDER); + notificationSpec.subject = intent.getStringExtra(EXTRA_NOTIFICATION_SUBJECT); + notificationSpec.title = intent.getStringExtra(EXTRA_NOTIFICATION_TITLE); + notificationSpec.body = intent.getStringExtra(EXTRA_NOTIFICATION_BODY); + notificationSpec.type = (NotificationType) intent.getSerializableExtra(EXTRA_NOTIFICATION_TYPE); + notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); + if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) { + notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber); + } + mDeviceSupport.onNotification(notificationSpec); break; } case ACTION_REBOOT: { @@ -246,7 +244,8 @@ public class DeviceCommunicationService extends Service { break; case ACTION_STARTAPP: { UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); - mDeviceSupport.onAppStart(uuid); + boolean start = intent.getBooleanExtra(EXTRA_APP_START, true); + mDeviceSupport.onAppStart(uuid, start); break; } case ACTION_DELETEAPP: { @@ -276,6 +275,7 @@ public class DeviceCommunicationService extends Service { /** * For testing! + * * @param factory */ public void setDeviceSupportFactory(DeviceSupportFactory factory) { @@ -285,6 +285,7 @@ public class DeviceCommunicationService extends Service { /** * Disposes the current DeviceSupport instance (if any) and sets a new device support instance * (if not null). + * * @param deviceSupport */ private void setDeviceSupport(@Nullable DeviceSupport deviceSupport) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java index a76285c18..bce4e64b7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupport.java @@ -22,14 +22,16 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; public interface DeviceSupport extends EventHandler { /** * Sets all context information needed for the instance to operate. - * @param gbDevice the device to operate with + * + * @param gbDevice the device to operate with * @param btAdapter the bluetooth adapter to use - * @param context the android context, e.g. to look up resources + * @param context the android context, e.g. to look up resources */ void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context); /** * Returns whether a transport-level connection is established with the device + * * @return whether the device is connected with the system running this software */ boolean isConnected(); @@ -40,9 +42,10 @@ public interface DeviceSupport extends EventHandler { * Returns true if a connection attempt was made. If the implementation is synchronous * it may also return true if the connection was successfully established, however * callers shall not rely on that. - * + *

    * The actual connection state change (successful or not) will be reported via the * #getDevice device as a device change Intent. + * * @see GBDevice#ACTION_DEVICE_CHANGED */ boolean connect(); @@ -62,6 +65,7 @@ public interface DeviceSupport extends EventHandler { /** * Attempts to pair and connect this device with the gadget device. Success * will be reported via a device change Intent. + * * @see GBDevice#ACTION_DEVICE_CHANGED */ void pair(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index d7bda277c..cd9718b41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -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.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; /** @@ -24,6 +25,7 @@ public class ServiceDeviceSupport implements DeviceSupport { THROTTLING, BUSY_CHECKING, } + private static final Logger LOG = LoggerFactory.getLogger(ServiceDeviceSupport.class); private static final long THROTTLING_THRESHOLD = 1000; // throttle multiple events in between one second @@ -111,27 +113,11 @@ public class ServiceDeviceSupport implements DeviceSupport { } @Override - public void onSMS(String from, String body) { - if (checkBusy("sms") || checkThrottle("sms")) { - return; - } - delegate.onSMS(from, body); - } - - @Override - public void onEmail(String from, String subject, String body) { - if (checkBusy("email") || checkThrottle("email")) { - return; - } - delegate.onEmail(from, subject, body); - } - - @Override - public void onGenericNotification(String title, String details, int handle) { + public void onNotification(NotificationSpec notificationSpec) { if (checkBusy("generic notification") || checkThrottle("generic notification")) { return; } - delegate.onGenericNotification(title, details, handle); + delegate.onNotification(notificationSpec); } @Override @@ -177,11 +163,11 @@ public class ServiceDeviceSupport implements DeviceSupport { } @Override - public void onAppStart(UUID uuid) { + public void onAppStart(UUID uuid, boolean start) { if (checkBusy("app start")) { return; } - delegate.onAppStart(uuid); + delegate.onAppStart(uuid, start); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java index 486215e36..9932a599f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java @@ -39,6 +39,8 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im public static final UUID UUID_DESCRIPTOR_CHARACTERISTIC_USER_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2901")); public static final UUID UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = UUID.fromString(String.format(BASE_UUID, "2902")); + //part of the generic BLE specs see https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.immediate_alert.xml + public static final UUID UUID_SERVICE_IMMEDIATE_ALERT = UUID.fromString((String.format(BASE_UUID, "1802"))); @Override public boolean connect() { @@ -140,13 +142,13 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im } private void gattServicesDiscovered(List discoveredGattServices) { - mAvailableCharacteristics = null; + if (discoveredGattServices == null) { return; } Set supportedServices = getSupportedServices(); - + mAvailableCharacteristics = new HashMap(); for (BluetoothGattService service : discoveredGattServices) { if (supportedServices.contains(service.getUuid())) { List characteristics = service.getCharacteristics(); @@ -154,10 +156,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im LOG.warn("Supported LE service " + service.getUuid() + "did not return any characteristics"); continue; } - mAvailableCharacteristics = new HashMap<>(characteristics.size()); + HashMap intmAvailableCharacteristics = new HashMap<>(characteristics.size()); for (BluetoothGattCharacteristic characteristic : characteristics) { - mAvailableCharacteristics.put(characteristic.getUuid(), characteristic); + intmAvailableCharacteristics.put(characteristic.getUuid(), characteristic); } + mAvailableCharacteristics.putAll(intmAvailableCharacteristics); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java index 3fe5aa698..165a72cbb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java @@ -14,10 +14,10 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; * Abstract base class for a BTLEOperation, i.e. an operation that does more than * just sending a few bytes to the device. It typically involves exchanging many messages * between the mobile and the device. - * + *

    * One operation may execute multiple @{link Transaction transactions} with each * multiple @{link BTLEAction actions}. - * + *

    * This class implements GattCallback so that subclasses may override those methods * to handle those events. * Note: by default all Gatt events are forwarded to AbstractBTLEDeviceSupport, subclasses may override @@ -33,6 +33,7 @@ public abstract class AbstractBTLEOperation /** * Delegates to the DeviceSupport instance and additionally sets this instance as the Gatt * callback for the transaction. + * * @param taskName * @return * @throws IOException diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java index 3b64e3d8d..f1c57d3d2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java @@ -2,8 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.btle; import java.io.IOException; -import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; - public interface BTLEOperation { public void perform() throws IOException; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java index a7f6d6a33..848989612 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java @@ -61,10 +61,10 @@ public final class BtLEQueue { while (!mDisposed && !mCrashed) { try { Transaction transaction = mTransactions.take(); - internalGattCallback.reset(); if (!isConnected()) { // TODO: request connection and initialization from the outside and wait until finished + internalGattCallback.reset(); // wait until the connection succeeds before running the actions // Note that no automatic connection is performed. This has to be triggered @@ -207,6 +207,7 @@ public final class BtLEQueue { /** * Depending on certain criteria, connects to the BluetoothGatt. + * * @return true if a reconnection attempt was made, or false otherwise */ private boolean maybeReconnect() { @@ -275,7 +276,9 @@ public final class BtLEQueue { // Implements callback methods for GATT events that the app cares about. For example, // connection change and services discovered. private final class InternalGattCallback extends BluetoothGattCallback { - private @Nullable GattCallback mTransactionGattCallback; + private + @Nullable + GattCallback mTransactionGattCallback; private GattCallback mExternalGattCallback; public InternalGattCallback(GattCallback externalGattCallback) { @@ -403,6 +406,8 @@ public final class BtLEQueue { } if (getCallbackToUse() != null) { getCallbackToUse().onCharacteristicChanged(gatt, characteristic); + } else { + LOG.info("No gattcallback registered, ignoring characteristic change"); } } @@ -442,7 +447,12 @@ public final class BtLEQueue { } public void reset() { + if (LOG.isDebugEnabled()) { + LOG.debug("internal gatt callback set to null"); + } mTransactionGattCallback = null; } - }; + } + + ; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java index 4006e1cd9..1fccb5287 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/Transaction.java @@ -19,7 +19,9 @@ public class Transaction { private String mName; private List mActions = new ArrayList<>(4); private long creationTimestamp = System.currentTimeMillis(); - private @Nullable GattCallback gattCallback; + private + @Nullable + GattCallback gattCallback; public Transaction(String taskName) { this.mName = taskName; @@ -57,7 +59,9 @@ public class Transaction { /** * Returns the GattCallback for this transaction, or null if none. */ - public @Nullable GattCallback getGattCallback() { + public + @Nullable + GattCallback getGattCallback() { return gattCallback; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java index 85eb3da6d..90efe84bc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/TransactionBuilder.java @@ -64,13 +64,16 @@ public class TransactionBuilder { /** * Sets a GattCallback instance that will be called when the transaction is executed, * resulting in GattCallback events. + * * @param callback the callback to set, may be null */ public void setGattCallback(@Nullable GattCallback callback) { mTransaction.setGattCallback(callback); } - public @Nullable GattCallback getGattCallback() { + public + @Nullable + GattCallback getGattCallback() { return mTransaction.getGattCallback(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java index 95c94d3ea..8312b5fd1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/SetProgressAction.java @@ -14,6 +14,7 @@ public class SetProgressAction extends PlainAction { /** * When run, will update the progress notification. + * * @param text * @param ongoing * @param percentage diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java index fa7d6e488..c866b7842 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/BatteryInfo.java @@ -11,7 +11,7 @@ public class BatteryInfo extends AbstractInfo { public static final byte DEVICE_BATTERY_CHARGING = 2; public static final byte DEVICE_BATTERY_CHARGING_FULL = 3; public static final byte DEVICE_BATTERY_CHARGE_OFF = 4; - + public BatteryInfo(byte[] data) { super(data); } @@ -43,7 +43,7 @@ public class BatteryInfo extends AbstractInfo { } public GregorianCalendar getLastChargeTime() { - GregorianCalendar lastCharge = new GregorianCalendar(); + GregorianCalendar lastCharge = MiBandDateConverter.createCalendar(); if (mData.length >= 10) { lastCharge = MiBandDateConverter.rawBytesToCalendar(new byte[]{ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index e01fc6509..98f2245b7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; import java.util.GregorianCalendar; import java.util.UUID; @@ -29,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; @@ -38,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactio import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.UpdateFirmwareOperation; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR; @@ -54,6 +58,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FL import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.FLASH_ORIGINAL_COLOUR; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_GENERIC; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_K9MAIL; +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.VIBRATION_COUNT; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_DURATION; @@ -404,18 +409,21 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } @Override - public void onSMS(String from, String body) { - performPreferredNotification("sms received", ORIGIN_SMS, null); - } - - @Override - public void onEmail(String from, String subject, String body) { - performPreferredNotification("email received", ORIGIN_K9MAIL, null); - } - - @Override - public void onGenericNotification(String title, String details, int handle) { - performPreferredNotification("generic notification received", ORIGIN_GENERIC, null); + public void onNotification(NotificationSpec notificationSpec) { + // FIXME: these ORIGIN contants do not really make sense anymore + switch (notificationSpec.type) { + case SMS: + performPreferredNotification("sms received", ORIGIN_SMS, null); + break; + case EMAIL: + performPreferredNotification("email received", ORIGIN_K9MAIL, null); + break; + case CHAT: + performPreferredNotification("chat message received", ORIGIN_PEBBLEMSG, null); + break; + default: + performPreferredNotification("generic notification received", ORIGIN_GENERIC, null); + } } @Override @@ -435,7 +443,10 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { * @param builder */ private MiBandSupport setCurrentTime(TransactionBuilder builder) { - byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(GregorianCalendar.getInstance()); + Calendar now = GregorianCalendar.getInstance(); + Date date = now.getTime(); + LOG.info("Sending current time to Mi Band: " + DateTimeUtils.formatDate(date) + " (" + date.toGMTString() + ")"); + byte[] nowBytes = MiBandDateConverter.calendarToRawBytes(now); byte[] time = new byte[]{ nowBytes[0], nowBytes[1], @@ -588,7 +599,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } @Override - public void onAppStart(UUID uuid) { + public void onAppStart(UUID uuid, boolean start) { // not supported } @@ -614,6 +625,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { handleNotificationNotif(characteristic.getValue()); } else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) { handleRealtimeSteps(characteristic.getValue()); + } else { + LOG.info("Unhandled characteristic changed: " + characteristicUUID); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java index cdf191287..486d3e411 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/FetchActivityOperation.java @@ -31,28 +31,89 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; +/** + * An operation that fetches activity data. For every fetch, a new operation must + * be created, i.e. an operation may not be reused for multiple fetches. + */ public class FetchActivityOperation extends AbstractBTLEOperation { private static final Logger LOG = LoggerFactory.getLogger(FetchActivityOperation.class); private static final byte[] fetch = new byte[]{MiBandService.COMMAND_FETCH_DATA}; //temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes) - private static final int activityDataHolderSize = 3 * 60 * 4; // 8h + private static final int activityDataHolderSize = 3 * 60 * 4; // 4h private static class ActivityStruct { - public byte[] activityDataHolder = new byte[activityDataHolderSize]; + private byte[] activityDataHolder = new byte[activityDataHolderSize]; //index of the buffer above - public int activityDataHolderProgress = 0; + private int activityDataHolderProgress = 0; //number of bytes we will get in a single data transfer, used as counter - public int activityDataRemainingBytes = 0; + private int activityDataRemainingBytes = 0; //same as above, but remains untouched for the ack message - public int activityDataUntilNextHeader = 0; + private int activityDataUntilNextHeader = 0; //timestamp of the single data transfer, incremented to store each minute's data - public GregorianCalendar activityDataTimestampProgress = null; + private GregorianCalendar activityDataTimestampProgress = null; //same as above, but remains untouched for the ack message - public GregorianCalendar activityDataTimestampToAck = null; + private GregorianCalendar activityDataTimestampToAck = null; + + public boolean hasRoomFor(byte[] value) { + return activityDataRemainingBytes >= value.length; + } + + public boolean isValidData(byte[] value) { + //I don't like this clause, but until we figure out why we get different data sometimes this should work + return value.length == 20 || value.length == activityDataRemainingBytes; + } + + public boolean isBufferFull() { + return activityDataHolderSize == activityDataHolderProgress; + } + + public void buffer(byte[] value) { + System.arraycopy(value, 0, activityDataHolder, activityDataHolderProgress, value.length); + activityDataHolderProgress += value.length; + activityDataRemainingBytes -= value.length; + + validate(); + } + + private void validate() { + GB.assertThat(activityDataRemainingBytes >= 0, "Illegal state, remaining bytes is negative"); + } + + public boolean isFirstChunk() { + return activityDataTimestampProgress == null; + } + + public void startNewBlock(GregorianCalendar timestamp, int dataUntilNextHeader) { + GB.assertThat(timestamp != null, "Timestamp must not be null"); + + if (isFirstChunk()) { + activityDataTimestampProgress = timestamp; + } else { + if (timestamp.getTimeInMillis() >= activityDataTimestampProgress.getTimeInMillis()) { + activityDataTimestampProgress = timestamp; + } else { + // something is fishy here... better not trust the given timestamp and simply + // (re)use the current one + // we do accept the timestamp to ack though, so that the bogus data is properly cleared on the band + } + } + activityDataTimestampToAck = (GregorianCalendar) timestamp.clone(); + activityDataRemainingBytes = activityDataUntilNextHeader = dataUntilNextHeader; + validate(); + } + + public boolean isBlockFinished() { + return activityDataRemainingBytes == 0; + } + + public void bufferFlushed(int minutes) { + activityDataTimestampProgress.add(Calendar.MINUTE, minutes); + activityDataHolderProgress = 0; + } } - private ActivityStruct activityStruct; + private ActivityStruct activityStruct = new ActivityStruct(); public FetchActivityOperation(MiBandSupport support) { super(support); @@ -90,40 +151,34 @@ public class FetchActivityOperation extends AbstractBTLEOperation * There are two kind of messages we currently know: * - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.) * - the second one is 20 bytes long and contains the actual activity data - * + *

    * The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called. - * @see #bufferActivityData(byte[]) - * * * @param value + * @see #bufferActivityData(byte[]) */ private void handleActivityNotif(byte[] value) { - boolean firstChunk = activityStruct == null; - if (firstChunk) { - activityStruct = new ActivityStruct(); - } - if (value.length == 11) { // byte 0 is the data type: 1 means that each minute is represented by a triplet of bytes int dataType = value[0]; // byte 1 to 6 represent a timestamp - GregorianCalendar timestamp = parseTimestamp(value, 1); + GregorianCalendar timestamp = MiBandDateConverter.rawBytesToCalendar(value, 1); // counter of all data held by the band int totalDataToRead = (value[7] & 0xff) | ((value[8] & 0xff) << 8); - totalDataToRead *= (dataType == 1) ? 3 : 1; + totalDataToRead *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1; // counter of this data block int dataUntilNextHeader = (value[9] & 0xff) | ((value[10] & 0xff) << 8); - dataUntilNextHeader *= (dataType == 1) ? 3 : 1; + dataUntilNextHeader *= (dataType == MiBandService.MODE_REGULAR_DATA_LEN_MINUTE) ? 3 : 1; - // there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1), + // there is a total of totalDataToRead that will come in chunks (3 bytes per minute if dataType == 1 (MiBandService.MODE_REGULAR_DATA_LEN_MINUTE)), // these chunks are usually 20 bytes long and grouped in blocks // after dataUntilNextHeader bytes we will get a new packet of 11 bytes that should be parsed // as we just did - if (firstChunk && dataUntilNextHeader != 0) { + if (activityStruct.isFirstChunk() && dataUntilNextHeader != 0) { GB.toast(getContext().getString(R.string.user_feedback_miband_activity_data_transfer, DateTimeUtils.formatDurationHoursMinutes((totalDataToRead / 3), TimeUnit.MINUTES), DateFormat.getDateTimeInstance().format(timestamp.getTime())), Toast.LENGTH_LONG, GB.INFO); @@ -132,37 +187,34 @@ public class FetchActivityOperation extends AbstractBTLEOperation LOG.info("data to read until next header: " + dataUntilNextHeader + " len: " + (dataUntilNextHeader / 3) + " minute(s)"); LOG.info("TIMESTAMP: " + DateFormat.getDateTimeInstance().format(timestamp.getTime()).toString() + " magic byte: " + dataUntilNextHeader); - activityStruct.activityDataRemainingBytes = activityStruct.activityDataUntilNextHeader = dataUntilNextHeader; - activityStruct.activityDataTimestampToAck = (GregorianCalendar) timestamp.clone(); - activityStruct.activityDataTimestampProgress = timestamp; + activityStruct.startNewBlock(timestamp, dataUntilNextHeader); } else { bufferActivityData(value); } LOG.debug("activity data: length: " + value.length + ", remaining bytes: " + activityStruct.activityDataRemainingBytes); - if (activityStruct.activityDataRemainingBytes == 0) { + GB.updateTransferNotification(getContext().getString(R.string.busy_task_fetch_activity_data), true, (int)(((float) (activityStruct.activityDataUntilNextHeader - activityStruct.activityDataRemainingBytes)) / activityStruct.activityDataUntilNextHeader * 100), getContext()); + + if (activityStruct.isBlockFinished()) { sendAckDataTransfer(activityStruct.activityDataTimestampToAck, activityStruct.activityDataUntilNextHeader); + GB.updateTransferNotification("", false, 100, getContext()); } } /** * Method to store temporarily the activity data values got from the Mi Band. - * + *

    * Since we expect chunks of 20 bytes each, we do not store the received bytes it the length is different. * * @param value */ private void bufferActivityData(byte[] value) { + if (activityStruct.hasRoomFor(value)) { + if (activityStruct.isValidData(value)) { + activityStruct.buffer(value); - if (activityStruct.activityDataRemainingBytes >= value.length) { - //I don't like this clause, but until we figure out why we get different data sometimes this should work - if (value.length == 20 || value.length == activityStruct.activityDataRemainingBytes) { - System.arraycopy(value, 0, activityStruct.activityDataHolder, activityStruct.activityDataHolderProgress, value.length); - activityStruct.activityDataHolderProgress += value.length; - activityStruct.activityDataRemainingBytes -= value.length; - - if (this.activityDataHolderSize == activityStruct.activityDataHolderProgress) { + if (activityStruct.isBufferFull()) { flushActivityDataHolder(); } } else { @@ -184,28 +236,32 @@ public class FetchActivityOperation extends AbstractBTLEOperation LOG.debug("nothing to flush, struct is already null"); return; } - LOG.debug("flushing activity data holder"); + LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / 3); byte category, intensity, steps; DBHandler dbHandler = null; try { dbHandler = GBApplication.acquireDB(); + int minutes = 0; try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples + int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000); for (int i = 0; i < activityStruct.activityDataHolderProgress; i += 3) { //TODO: check if multiple of 3, if not something is wrong category = activityStruct.activityDataHolder[i]; intensity = activityStruct.activityDataHolder[i + 1]; steps = activityStruct.activityDataHolder[i + 2]; dbHandler.addGBActivitySample( - (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000), + timestampInSeconds, SampleProvider.PROVIDER_MIBAND, (short) (intensity & 0xff), (short) (steps & 0xff), category); - activityStruct.activityDataTimestampProgress.add(Calendar.MINUTE, 1); + // next minute + minutes++; + timestampInSeconds += 60; } } finally { - activityStruct.activityDataHolderProgress = 0; + activityStruct.bufferFlushed(minutes); } } catch (Exception ex) { GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR); @@ -218,7 +274,7 @@ public class FetchActivityOperation extends AbstractBTLEOperation /** * Acknowledge the transfer of activity data to the Mi Band. - * + *

    * After receiving data from the band, it has to be acknowledged. This way the Mi Band will delete * the data it has on record. * @@ -255,7 +311,7 @@ public class FetchActivityOperation extends AbstractBTLEOperation builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), ack); builder.queue(getQueue()); - // flush to the DB after sending the ACK + // flush to the DB after queueing the ACK flushActivityDataHolder(); //The last data chunk sent by the miband has always length 0. @@ -273,15 +329,4 @@ public class FetchActivityOperation extends AbstractBTLEOperation LOG.error("Unable to send ack to MI", ex); } } - - private GregorianCalendar parseTimestamp(byte[] value, int offset) { - GregorianCalendar timestamp = new GregorianCalendar( - value[offset] + 2000, - value[offset + 1], - value[offset + 2], - value[offset + 3], - value[offset + 4], - value[offset + 5]); - return timestamp; - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java index 35448d5d6..0d7c71d6d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java @@ -72,21 +72,21 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation * Upon receiving known values that request further action by GB, the appropriate method is called. * * @param value */ private void handleNotificationNotif(byte[] value) { - if(value.length != 1) { + if (value.length != 1) { LOG.error("Notifications should be 1 byte long."); getSupport().logMessageContent(value); return; } switch (value[0]) { case MiBandService.NOTIFY_FW_CHECK_SUCCESS: - if(firmwareInfoSent && newFirmware != null) { - if(sendFirmwareData(newFirmware)) { + if (firmwareInfoSent && newFirmware != null) { + if (sendFirmwareData(newFirmware)) { rebootWhenBandReady = true; } else { //TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do? @@ -131,14 +131,14 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation * The Mi Band will send a notification after receiving these data to confirm if the metadata looks good to it. - * @see MiBandSupport#handleNotificationNotif * * @param currentFwVersion * @param newFwVersion * @param newFwSize * @param checksum + * @see MiBandSupport#handleNotificationNotif */ private void sendFirmwareInfo(int currentFwVersion, int newFwVersion, int newFwSize, int checksum) throws IOException { byte[] fwInfo = new byte[]{ @@ -165,13 +165,13 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation * The Mi Band will send a notification after receiving these data to confirm if the firmware looks good to it. - * @see MiBandSupport#handleNotificationNotif * * @param fwbytes * @return whether the transfer succeeded or not. Only a BT layer exception will cause the transmission to fail. - * */ + * @see MiBandSupport#handleNotificationNotif + */ private boolean sendFirmwareData(byte fwbytes[]) { int len = fwbytes.length; final int packetLength = 20; @@ -190,10 +190,10 @@ public class UpdateFirmwareOperation extends AbstractBTLEOperation 0) && (i % 50 == 0)) { builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC}); - builder.add(new SetProgressAction("Firmware update in progress", true, (firmwareProgress / len) * 100, getContext())); + builder.add(new SetProgressAction("Firmware update in progress", true, (int)(((float) firmwareProgress) / len * 100), getContext())); } - LOG.info("Firmware update progress:" + firmwareProgress + " total len:" + len + " progress:" + (firmwareProgress / len)); + LOG.info("Firmware update progress:" + firmwareProgress + " total len:" + len + " progress:" + (int)(((float) firmwareProgress) / len * 100)); } if (!(len % packetLength == 0)) { 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 76a71483d..45c48d37a 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 @@ -3,12 +3,17 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.Uri; import android.os.ParcelUuid; import android.preference.PreferenceManager; +import org.json.JSONArray; +import org.json.JSONException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,12 +25,14 @@ import java.net.InetAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.UUID; import java.util.zip.ZipInputStream; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; @@ -38,8 +45,24 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; public class PebbleIoThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class); + + 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"; + public static final String PEBBLEKIT_ACTION_APP_NACK = "com.getpebble.action.app.NACK"; + public static final String PEBBLEKIT_ACTION_APP_RECEIVE = "com.getpebble.action.app.RECEIVE"; + public static final String PEBBLEKIT_ACTION_APP_RECEIVE_ACK = "com.getpebble.action.app.RECEIVE_ACK"; + public static final String PEBBLEKIT_ACTION_APP_RECEIVE_NACK = "com.getpebble.action.app.RECEIVE_NACK"; + public static final String PEBBLEKIT_ACTION_APP_SEND = "com.getpebble.action.app.SEND"; + public static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START"; + public static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP"; + + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + private final PebbleProtocol mPebbleProtocol; private final PebbleSupport mPebbleSupport; + private final boolean mEnablePebblekit; + private boolean mIsTCP = false; private BluetoothAdapter mBtAdapter = null; private BluetoothSocket mBtSocket = null; @@ -49,7 +72,6 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean mQuit = false; private boolean mIsConnected = false; private boolean mIsInstalling = false; - private int mConnectionAttempts = 0; private PBWReader mPBWReader = null; private int mAppInstallToken = -1; @@ -62,11 +84,77 @@ public class PebbleIoThread extends GBDeviceIoThread { private int mBinarySize = -1; private int mBytesWritten = -1; + private final BroadcastReceiver mPebbleKitReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + LOG.info("Got action: " + action); + UUID uuid; + switch (action) { + case PEBBLEKIT_ACTION_APP_START: + case PEBBLEKIT_ACTION_APP_STOP: + uuid = (UUID) intent.getSerializableExtra("uuid"); + if (uuid != null) { + write(mPebbleProtocol.encodeAppStart(uuid, action.equals(PEBBLEKIT_ACTION_APP_START))); + } + break; + case PEBBLEKIT_ACTION_APP_SEND: + int transaction_id = intent.getIntExtra("transaction_id", -1); + uuid = (UUID) intent.getSerializableExtra("uuid"); + String jsonString = intent.getStringExtra("msg_data"); + LOG.info("json string: " + jsonString); + + try { + JSONArray jsonArray = new JSONArray(jsonString); + write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray)); + sendAppMessageAck(transaction_id); + + } catch (JSONException e) { + e.printStackTrace(); + } + break; + case PEBBLEKIT_ACTION_APP_ACK: + // we do not get a uuid and cannot map a transaction id to it, so we ack in PebbleProtocol early + /* + uuid = (UUID) intent.getSerializableExtra("uuid"); + int transaction_id = intent.getIntExtra("transaction_id", -1); + if (transaction_id >= 0 && transaction_id <= 255) { + write(mPebbleProtocol.encodeApplicationMessageAck(uuid, (byte) transaction_id)); + } else { + LOG.warn("illegal transacktion id " + transaction_id); + } + */ + break; + } + } + }; + + private void sendAppMessageIntent(GBDeviceEventAppMessage appMessage) { + Intent intent = new Intent(); + intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE); + intent.putExtra("uuid", appMessage.appUUID); + intent.putExtra("msg_data", appMessage.message); + intent.putExtra("transaction_id", appMessage.id); + LOG.info("broadcasting to uuid " + appMessage.appUUID + " transaction id: " + appMessage.id + " JSON: " + appMessage.message); + getContext().sendBroadcast(intent); + } + + private void sendAppMessageAck(int transactionId) { + if (transactionId > 0 && transactionId <= 255) { + Intent intent = new Intent(); + intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE_ACK); + intent.putExtra("transaction_id", transactionId); + LOG.info("broadcasting ACK (transaction id " + transactionId + ")"); + getContext().sendBroadcast(intent); + } + } + public PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) { super(gbDevice, context); mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol; mBtAdapter = btAdapter; mPebbleSupport = pebbleSupport; + mEnablePebblekit = sharedPrefs.getBoolean("pebble_enable_pebblekit", false); } @@ -100,7 +188,6 @@ public class PebbleIoThread extends GBDeviceIoThread { return false; } - SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); mPebbleProtocol.setForceProtocol(sharedPrefs.getBoolean("pebble_force_protocol", false)); gbDevice.setState(GBDevice.State.CONNECTED); gbDevice.sendDeviceUpdateIntent(getContext()); @@ -116,6 +203,7 @@ public class PebbleIoThread extends GBDeviceIoThread { gbDevice.sendDeviceUpdateIntent(getContext()); mIsConnected = connect(gbDevice.getAddress()); + enablePebbleKitReceiver(mIsConnected); mQuit = !mIsConnected; // quit if not connected byte[] buffer = new byte[8192]; @@ -261,14 +349,14 @@ public class PebbleIoThread extends GBDeviceIoThread { LOG.info(e.getMessage()); gbDevice.setState(GBDevice.State.CONNECTING); gbDevice.sendDeviceUpdateIntent(getContext()); - - while (mConnectionAttempts++ < 10 && !mQuit) { - LOG.info("Trying to reconnect (attempt " + mConnectionAttempts + ")"); + mIsConnected = false; + int reconnectAttempts = Integer.valueOf(sharedPrefs.getString("pebble_reconnect_attempts", "10")); + while (reconnectAttempts-- > 0 && !mQuit) { + LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")"); mIsConnected = connect(gbDevice.getAddress()); if (mIsConnected) break; } - mConnectionAttempts = 0; if (!mIsConnected) { mBtSocket = null; LOG.info("Bluetooth socket closed, will quit IO Thread"); @@ -285,11 +373,36 @@ public class PebbleIoThread extends GBDeviceIoThread { e.printStackTrace(); } } + enablePebbleKitReceiver(false); mBtSocket = null; gbDevice.setState(GBDevice.State.NOT_CONNECTED); gbDevice.sendDeviceUpdateIntent(getContext()); } + private void enablePebbleKitReceiver(boolean enable) { + + if (enable && mEnablePebblekit) { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(PEBBLEKIT_ACTION_APP_ACK); + intentFilter.addAction(PEBBLEKIT_ACTION_APP_NACK); + intentFilter.addAction(PEBBLEKIT_ACTION_APP_SEND); + intentFilter.addAction(PEBBLEKIT_ACTION_APP_START); + intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP); + try { + getContext().registerReceiver(mPebbleKitReceiver, intentFilter); + } catch (IllegalArgumentException e) { + // ignore + } + } else { + try { + getContext().unregisterReceiver(mPebbleKitReceiver); + } catch (IllegalArgumentException e) { + // ignore + } + } + } + + private void write_real(byte[] bytes) { try { if (mIsTCP) { @@ -322,7 +435,6 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean evaluateGBDeviceEventPebble(GBDeviceEvent deviceEvent) { if (deviceEvent instanceof GBDeviceEventVersionInfo) { - SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); if (sharedPrefs.getBoolean("datetime_synconconnect", true)) { LOG.info("syncing time"); write(mPebbleProtocol.encodeSetTime()); @@ -396,7 +508,13 @@ public class PebbleIoThread extends GBDeviceIoThread { GBDeviceEventAppInfo appInfoEvent = (GBDeviceEventAppInfo) deviceEvent; setInstallSlot(appInfoEvent.freeSlot); return false; + } else if (deviceEvent instanceof GBDeviceEventAppMessage) { + if (mEnablePebblekit) { + LOG.info("Got AppMessage event"); + sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent); + } } + return false; } @@ -423,7 +541,17 @@ public class PebbleIoThread extends GBDeviceIoThread { return; } - mPBWReader = new PBWReader(uri, getContext(), gbDevice.getHardwareVersion().equals("dvt") ? "basalt" : "aplite"); + String hwRev = gbDevice.getHardwareVersion(); + String platformName; + if (hwRev.startsWith("snowy")) { + platformName = "basalt"; + } else if (hwRev.startsWith("spalding")) { + platformName = "chalk"; + } else { + platformName = "aplite"; + } + + mPBWReader = new PBWReader(uri, getContext(), platformName); mPebbleInstallables = mPBWReader.getPebbleInstallables(); mCurrentInstallableIndex = 0; 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 84a57cee4..1eda96881 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 @@ -1,7 +1,11 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; +import android.util.Base64; import android.util.Pair; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,13 +20,18 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor; +import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; @@ -60,6 +69,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final short ENDPOINT_PUTBYTES = (short) 48879; static final byte APPRUNSTATE_START = 1; + static final byte APPRUNSTATE_STOP = 2; static final byte BLOBDB_INSERT = 1; static final byte BLOBDB_DELETE = 4; @@ -70,6 +80,9 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte BLOBDB_REMINDER = 3; static final byte BLOBDB_NOTIFICATION = 4; + // This is not in the Pebble protocol + static final byte NOTIFICATION_UNDEFINED = -1; + static final byte NOTIFICATION_EMAIL = 0; static final byte NOTIFICATION_SMS = 1; static final byte NOTIFICATION_TWITTER = 2; @@ -178,8 +191,8 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte TYPE_BYTEARRAY = 0; static final byte TYPE_CSTRING = 1; - static final byte TYPE_UINT32 = 2; - static final byte TYPE_INT32 = 3; + static final byte TYPE_UINT = 2; + static final byte TYPE_INT = 3; static final short LENGTH_PREFIX = 4; static final short LENGTH_SIMPLEMESSAGE = 1; @@ -202,7 +215,17 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte LENGTH_UUID = 16; - private static final String[] hwRevisions = {"unknown", "ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0", "evt2", "dvt"}; + // base is -5 + private static final String[] hwRevisions = { + // Emulator + "spalding_bb2", "snowy_bb2", "snowy_bb", "bb2", "bb", + "unknown", + // Pebble + "ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0", + // Pebble Time + "snowy_evt2", "snowy_dvt", "spalding_dvt", "snowy_s3", "spalding" + }; + private static Random mRandom = new Random(); boolean isFw3x = false; @@ -362,7 +385,21 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - private byte[] encodeNotification(int id, String title, String subtitle, String body, byte type, boolean hasHandle) { + @Override + public byte[] encodeNotification(NotificationSpec notificationSpec) { + boolean hasHandle = notificationSpec.id != -1; + int id = notificationSpec.id != -1 ? notificationSpec.id : mRandom.nextInt(); + String title; + String subtitle = null; + + // for SMS and EMAIL that came in though SMS or K9 receiver + if (notificationSpec.sender != null) { + title = notificationSpec.sender; + subtitle = notificationSpec.subject; + } else { + title = notificationSpec.title; + } + Long ts = System.currentTimeMillis(); if (!isFw3x) { ts += (SimpleTimeZone.getDefault().getOffset(ts)); @@ -371,32 +408,19 @@ public class PebbleProtocol extends GBDeviceProtocol { if (isFw3x) { // 3.x notification - return encodeBlobdbNotification(id, (int) (ts & 0xffffffff), title, subtitle, body, type, hasHandle); - } else if (mForceProtocol || type != NOTIFICATION_EMAIL) { + //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); + } else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) { // 2.x notification - return encodeExtensibleNotification(id, (int) (ts & 0xffffffff), title, subtitle, body, type, hasHandle); + return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle); } else { // 1.x notification on FW 2.X - String[] parts = {title, body, ts.toString(), subtitle}; - return encodeMessage(ENDPOINT_NOTIFICATION, type, 0, parts); + String[] parts = {title, notificationSpec.body, ts.toString(), subtitle}; + // be aware that type is at this point always NOTIFICATION_EMAIL + return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, 0, parts); } } - @Override - public byte[] encodeSMS(String from, String body) { - return encodeNotification(mRandom.nextInt(), from, null, body, NOTIFICATION_SMS, false); - } - - @Override - public byte[] encodeEmail(String from, String subject, String body) { - return encodeNotification(mRandom.nextInt(), from, subject, body, NOTIFICATION_EMAIL, false); - } - - @Override - public byte[] encodeGenericNotification(String title, String details, int handle) { - return encodeNotification(handle, title, null, details, NOTIFICATION_SMS, true); - } - @Override public byte[] encodeSetTime() { long ts = System.currentTimeMillis(); @@ -431,7 +455,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeSetCallState("Where are you?", "Gadgetbridge", start ? ServiceCommand.CALL_INCOMING : ServiceCommand.CALL_END); } - private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, byte type, boolean hasHandle) { + private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle) { final short ACTION_LENGTH_MIN = 10; String[] parts = {title, subtitle, body}; @@ -441,17 +465,23 @@ public class PebbleProtocol extends GBDeviceProtocol { short actions_length; String dismiss_string; String open_string = "Open on phone"; + String mute_string = "Mute"; + if (sourceName != null) { + mute_string += " " + sourceName; + } + byte dismiss_action_id; + if (hasHandle) { - actions_count = 2; + actions_count = 3; dismiss_string = "Dismiss"; dismiss_action_id = 0x02; - actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length()); + actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length); } else { actions_count = 1; dismiss_string = "Dismiss all"; dismiss_action_id = 0x03; - actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length()); + actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length); } byte attributes_count = 0; @@ -509,17 +539,25 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put((byte) 0x04); // dismiss buf.put((byte) 0x01); // number attributes buf.put((byte) 0x01); // attribute id (title) - buf.putShort((short) dismiss_string.length()); + buf.putShort((short) dismiss_string.getBytes().length); buf.put(dismiss_string.getBytes()); // open action if (hasHandle) { buf.put((byte) 0x01); - buf.put((byte) 0x02); // dissmiss - FIXME: find out how to answer to 2.x generic actions + buf.put((byte) 0x02); // generic buf.put((byte) 0x01); // number attributes buf.put((byte) 0x01); // attribute id (title) - buf.putShort((short) open_string.length()); + buf.putShort((short) open_string.getBytes().length); buf.put(open_string.getBytes()); + + buf.put((byte) 0x04); + buf.put((byte) 0x02); // generic + buf.put((byte) 0x01); // number attributes + buf.put((byte) 0x01); // attribute id (title) + buf.putShort((short) mute_string.getBytes().length); + buf.put(mute_string.getBytes()); + } return buf.array(); @@ -556,40 +594,116 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } - private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, byte type, boolean hasHandle) { + private byte[] encodeTimelinePin(int id, int timestamp, short duration, int icon_id, String title) { + 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; + + int attributes_length = 10 + title.getBytes().length; + int pin_length = TIMELINE_PIN_LENGTH + attributes_length; + ByteBuffer buf = ByteBuffer.allocate(pin_length); + + // pin - 46 bytes + buf.order(ByteOrder.BIG_ENDIAN); + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits()); + buf.putLong(0); // parent + buf.putLong(0); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(timestamp); // 32-bit timestamp + buf.putShort(duration); + buf.put((byte) 0x02); // type (0x02 = pin) + buf.putShort((short) 0x0001); // flags 0x0001 = ? + buf.put((byte) 0x02); // layout (0x02 = pin?) + + buf.putShort((short) attributes_length); // total length of all attributes and actions in bytes + buf.put(attributes_count); + buf.put(actions_count); + + buf.put((byte) 4); // icon + buf.putShort((short) 4); // length of int + buf.putInt(icon_id); + buf.put((byte) 1); // title + buf.putShort((short) title.getBytes().length); + buf.put(title.getBytes()); + + return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array()); + } + + private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType) { final short NOTIFICATION_PIN_LENGTH = 46; final short ACTION_LENGTH_MIN = 10; String[] parts = {title, subtitle, body}; - int icon_id = 0x80000000 | 1; - switch (type) { - case NOTIFICATION_EMAIL: - icon_id = 0x80000000 | 19; + int icon_id; + byte color_id; + switch (notificationType) { + case EMAIL: + icon_id = PebbleIconID.GENERIC_EMAIL; + color_id = PebbleColor.JaegerGreen; + break; + case SMS: + icon_id = PebbleIconID.GENERIC_SMS; + color_id = PebbleColor.VividViolet; + break; + default: + switch (notificationType) { + case TWITTER: + icon_id = PebbleIconID.NOTIFICATION_TWITTER; + color_id = PebbleColor.BlueMoon; + break; + case EMAIL: + icon_id = PebbleIconID.GENERIC_EMAIL; + color_id = PebbleColor.JaegerGreen; + break; + case SMS: + icon_id = PebbleIconID.GENERIC_SMS; + color_id = PebbleColor.VividViolet; + break; + case FACEBOOK: + icon_id = PebbleIconID.NOTIFICATION_FACEBOOK; + color_id = PebbleColor.VeryLightBlue; + break; + case CHAT: + icon_id = PebbleIconID.NOTIFICATION_HIPCHAT; + color_id = PebbleColor.Inchworm; + break; + default: + icon_id = PebbleIconID.NOTIFICATION_GENERIC; + color_id = PebbleColor.Red; + break; + } break; - case NOTIFICATION_SMS: - icon_id = 0x80000000 | 45; } // Calculate length first byte actions_count; short actions_length; String dismiss_string; String open_string = "Open on phone"; + String mute_string = "Mute"; + if (sourceName != null) { + mute_string += " " + sourceName; + } + byte dismiss_action_id; if (hasHandle) { - actions_count = 2; + actions_count = 3; dismiss_string = "Dismiss"; dismiss_action_id = 0x02; - actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length()); + actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length); } else { actions_count = 1; dismiss_string = "Dismiss all"; dismiss_action_id = 0x03; - actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.length() + open_string.length()); + actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length); } - byte attributes_count = 1; // icon - short attributes_length = (short) (7 + actions_length); // icon + byte attributes_count = 2; // icon + short attributes_length = (short) (11 + actions_length); if (parts != null) { for (String s : parts) { if (s == null || s.equals("")) { @@ -642,30 +756,41 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put((byte) 4); // icon buf.putShort((short) 4); // length of int - buf.putInt(icon_id); + buf.putInt(0x80000000 | icon_id); + + buf.put((byte) 28); // background_color + buf.putShort((short) 1); // length of int + buf.put(color_id); // dismiss action buf.put(dismiss_action_id); buf.put((byte) 0x02); // generic action, dismiss did not do anything buf.put((byte) 0x01); // number attributes buf.put((byte) 0x01); // attribute id (title) - buf.putShort((short) dismiss_string.length()); + buf.putShort((short) dismiss_string.getBytes().length); buf.put(dismiss_string.getBytes()); - // open action + // open and mute actions if (hasHandle) { buf.put((byte) 0x01); buf.put((byte) 0x02); // generic action buf.put((byte) 0x01); // number attributes buf.put((byte) 0x01); // attribute id (title) - buf.putShort((short) open_string.length()); + buf.putShort((short) open_string.getBytes().length); buf.put(open_string.getBytes()); + + buf.put((byte) 0x04); + buf.put((byte) 0x02); // generic action + buf.put((byte) 0x01); // number attributes + buf.put((byte) 0x01); // attribute id (title) + buf.putShort((short) mute_string.getBytes().length); + buf.put(mute_string.getBytes()); } return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array()); } - public byte[] encodeActionResponse2x(int id, int iconId, String caption) { - short length = (short) (18 + caption.length()); + public byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) { + short length = (short) (18 + caption.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(length); @@ -673,20 +798,20 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.order(ByteOrder.LITTLE_ENDIAN); buf.put(NOTIFICATIONACTION_RESPONSE); buf.putInt(id); - buf.put((byte) 0x01); // action id? + buf.put(actionId); buf.put(NOTIFICATIONACTION_ACK); buf.put((byte) 2); //nr of attributes buf.put((byte) 6); // icon buf.putShort((short) 4); // length buf.putInt(iconId); buf.put((byte) 2); // title - buf.putShort((short) caption.length()); + buf.putShort((short) caption.getBytes().length); buf.put(caption.getBytes()); return buf.array(); } public byte[] encodeActionResponse(UUID uuid, int iconId, String caption) { - short length = (short) (29 + caption.length()); + short length = (short) (29 + caption.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(length); @@ -701,7 +826,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.putShort((short) 4); // length buf.putInt(0x80000000 | iconId); buf.put((byte) 2); // title - buf.putShort((short) caption.length()); + buf.putShort((short) caption.getBytes().length); buf.put(caption.getBytes()); return buf.array(); } @@ -710,7 +835,7 @@ public class PebbleProtocol extends GBDeviceProtocol { final short METADATA_LENGTH = 126; byte[] name_buf = new byte[96]; - System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.length()); + System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.getBytes().length); ByteBuffer buf = ByteBuffer.allocate(METADATA_LENGTH); buf.order(ByteOrder.BIG_ENDIAN); @@ -794,19 +919,20 @@ public class PebbleProtocol extends GBDeviceProtocol { } @Override - public byte[] encodeAppStart(UUID uuid) { + public byte[] encodeAppStart(UUID uuid, boolean start) { if (isFw3x) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_APPRUNSTATE); buf.putShort(ENDPOINT_APPRUNSTATE); - buf.put(APPRUNSTATE_START); + buf.put(start ? APPRUNSTATE_START : APPRUNSTATE_STOP); buf.putLong(uuid.getMostSignificantBits()); buf.putLong(uuid.getLeastSignificantBits()); return buf.array(); } else { ArrayList> pairs = new ArrayList<>(); - pairs.add(new Pair<>(1, (Object) 1)); // launch + int param = start ? 1 : 0; + pairs.add(new Pair<>(1, (Object) param)); return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs); } } @@ -1034,10 +1160,10 @@ public class PebbleProtocol extends GBDeviceProtocol { while (dictSize-- > 0) { Integer key = buf.getInt(); byte type = buf.get(); - short length = buf.getShort(); // length + short length = buf.getShort(); switch (type) { - case TYPE_INT32: - case TYPE_UINT32: + case TYPE_INT: + case TYPE_UINT: dict.add(new Pair(key, buf.getInt())); break; case TYPE_CSTRING: @@ -1056,16 +1182,89 @@ public class PebbleProtocol extends GBDeviceProtocol { return dict; } + private GBDeviceEvent[] decodeDictToJSONAppMessage(UUID uuid, ByteBuffer buf) throws JSONException { + buf.order(ByteOrder.LITTLE_ENDIAN); + byte dictSize = buf.get(); + if (dictSize == 0) { + LOG.info("dict size is 0, ignoring"); + return null; + } + JSONArray jsonArray = new JSONArray(); + while (dictSize-- > 0) { + JSONObject jsonObject = new JSONObject(); + Integer key = buf.getInt(); + byte type = buf.get(); + short length = buf.getShort(); + jsonObject.put("key", key); + jsonObject.put("length", length); + switch (type) { + case TYPE_UINT: + jsonObject.put("type", "uint"); + if (length == 1) { + jsonObject.put("value", buf.get() & 0xff); + } else if (length == 2) { + jsonObject.put("value", buf.getShort() & 0xffff); + } else { + jsonObject.put("value", buf.getInt() & 0xffffffffL); + } + break; + case TYPE_INT: + jsonObject.put("type", "int"); + if (length == 1) { + jsonObject.put("value", buf.get()); + } else if (length == 2) { + jsonObject.put("value", buf.getShort()); + } else { + jsonObject.put("value", buf.getInt()); + } + break; + case TYPE_BYTEARRAY: + case TYPE_CSTRING: + byte[] bytes = new byte[length]; + buf.get(bytes); + if (type == TYPE_BYTEARRAY) { + jsonObject.put("type", "bytes"); + jsonObject.put("value", Base64.encode(bytes, Base64.NO_WRAP)); + } else { + jsonObject.put("type", "string"); + jsonObject.put("value", Arrays.toString(bytes)); + } + break; + default: + LOG.info("unknown type in appmessage, ignoring"); + return null; + } + jsonArray.put(jsonObject); + } + + // this is a hack we send an ack to the Pebble immediately because we cannot map the transaction_id from the intent back to a uuid yet + GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); + + GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage(); + appMessage.appUUID = uuid; + appMessage.id = last_id & 0xff; + appMessage.message = jsonArray.toString(); + return new GBDeviceEvent[]{appMessage, sendBytesAck}; + } + byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs) { int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict) for (Pair pair : pairs) { length += 7; // key + type + length if (pair.second instanceof Integer) { length += 4; + } else if (pair.second instanceof Short) { + length += 2; + } else if (pair.second instanceof Byte) { + length += 1; } else if (pair.second instanceof String) { - length += ((String) pair.second).length() + 1; + length += ((String) pair.second).getBytes().length + 1; + } else if (pair.second instanceof byte[]) { + length += ((byte[]) pair.second).length; } } + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) length); @@ -1076,24 +1275,77 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.putLong(uuid.getLeastSignificantBits()); buf.put((byte) pairs.size()); - buf.order(ByteOrder.LITTLE_ENDIAN); // Um, yes, really + buf.order(ByteOrder.LITTLE_ENDIAN); for (Pair pair : pairs) { buf.putInt(pair.first); if (pair.second instanceof Integer) { - buf.put(TYPE_INT32); - buf.putShort((short) 4); // length of int + buf.put(TYPE_INT); + buf.putShort((short) 4); // length buf.putInt((int) pair.second); + } else if (pair.second instanceof Short) { + buf.put(TYPE_INT); + buf.putShort((short) 2); // length + buf.putShort((short) pair.second); + } else if (pair.second instanceof Byte) { + buf.put(TYPE_INT); + buf.putShort((short) 1); // length + buf.put((byte) pair.second); } else if (pair.second instanceof String) { + String str = (String) pair.second; buf.put(TYPE_CSTRING); - buf.putShort((short) (((String) pair.second).length() + 1)); - buf.put(((String) pair.second).getBytes()); + buf.putShort((short) (str.getBytes().length + 1)); + buf.put(str.getBytes()); buf.put((byte) 0); + } else if (pair.second instanceof byte[]) { + byte[] bytes = (byte[]) pair.second; + buf.put(TYPE_BYTEARRAY); + buf.putShort((short) bytes.length); + buf.put(bytes); } } return buf.array(); } + public byte[] encodeApplicationMessageFromJSON(UUID uuid, JSONArray jsonArray) { + ArrayList> pairs = new ArrayList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + try { + JSONObject jsonObject = (JSONObject) jsonArray.get(i); + String type = (String) jsonObject.get("type"); + int key = (int) jsonObject.get("key"); + int length = (int) jsonObject.get("length"); + switch (type) { + case "uint": + case "int": + if (length == 1) { + pairs.add(new Pair<>(key, (Object) (byte) jsonObject.getInt("value"))); + } else if (length == 2) { + pairs.add(new Pair<>(key, (Object) (short) jsonObject.getInt("value"))); + } else { + if (type.equals("uint")) { + pairs.add(new Pair<>(key, (Object) (int) (jsonObject.getInt("value") & 0xffffffffL))); + } else { + pairs.add(new Pair<>(key, (Object) jsonObject.getInt("value"))); + } + } + break; + case "string": + pairs.add(new Pair<>(key, (Object) jsonObject.getString("value"))); + break; + case "bytes": + byte[] bytes = Base64.decode(jsonObject.getString("value"), Base64.NO_WRAP); + pairs.add(new Pair<>(key, (Object) bytes)); + break; + } + } catch (JSONException e) { + return null; + } + } + + return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs); + } + private static byte reverseBits(byte in) { byte out = 0; for (int i = 0; i < 8; i++) { @@ -1160,7 +1412,7 @@ public class PebbleProtocol extends GBDeviceProtocol { if (command == 0x02) { int id = buf.getInt(); byte action = buf.get(); - if (action >= 0x01 && action <= 0x03) { + if (action >= 0x01 && action <= 0x04) { GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl(); devEvtNotificationControl.handle = id; GBDeviceEventSendBytes sendBytesAck = null; @@ -1169,7 +1421,7 @@ public class PebbleProtocol extends GBDeviceProtocol { case 0x01: devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN; sendBytesAck = new GBDeviceEventSendBytes(); - sendBytesAck.encodedBytes = encodeActionResponse2x(id, 6, "Opened"); + sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Opened"); break; case 0x02: devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS; @@ -1177,6 +1429,11 @@ public class PebbleProtocol extends GBDeviceProtocol { case 0x03: devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL; break; + case 0x04: + devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE; + sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Muted"); + break; default: return null; } @@ -1198,7 +1455,7 @@ public class PebbleProtocol extends GBDeviceProtocol { long uuid_low = buf.getLong(); int id = (int) (uuid_low & 0xffffffff); byte action = buf.get(); - if (action >= 0x01 && action <= 0x03) { + if (action >= 0x01 && action <= 0x04) { GBDeviceEventNotificationControl dismissNotification = new GBDeviceEventNotificationControl(); dismissNotification.handle = id; String caption = "undefined"; @@ -1207,17 +1464,22 @@ public class PebbleProtocol extends GBDeviceProtocol { case 0x01: dismissNotification.event = GBDeviceEventNotificationControl.Event.OPEN; caption = "Opened"; - icon_id = 49; + icon_id = PebbleIconID.DURING_PHONE_CALL; break; case 0x02: dismissNotification.event = GBDeviceEventNotificationControl.Event.DISMISS; caption = "Dismissed"; - icon_id = 51; + icon_id = PebbleIconID.RESULT_DISMISSED; break; case 0x03: dismissNotification.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL; caption = "All dismissed"; - icon_id = 51; + icon_id = PebbleIconID.RESULT_DISMISSED; + break; + case 0x04: + dismissNotification.event = GBDeviceEventNotificationControl.Event.MUTE; + caption = "Muted"; + icon_id = PebbleIconID.RESULT_MUTE; break; } GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); @@ -1361,11 +1623,9 @@ public class PebbleProtocol extends GBDeviceProtocol { } buf.get(tmp, 0, 9); - Byte hwRev = buf.get(); - if (hwRev > 0 && hwRev < hwRevisions.length) { + int hwRev = buf.get() + 5; + if (hwRev >= 0 && hwRev < hwRevisions.length) { versionCmd.hwVersion = hwRevisions[hwRev]; - } else if (hwRev == -3) { // basalt emulator - versionCmd.hwVersion = "dvt"; } devEvts = new GBDeviceEvent[]{versionCmd}; break; @@ -1478,6 +1738,13 @@ public class PebbleProtocol extends GBDeviceProtocol { } else if (GadgetbridgePblSupport.uuid.equals(uuid)) { ArrayList> dict = decodeDict(buf); devEvts = mGadgetbridgePblSupport.handleMessage(dict); + } else { + try { + devEvts = decodeDictToJSONAppMessage(uuid, buf); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } } break; case APPLICATIONMESSAGE_ACK: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/WeatherNeatSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/WeatherNeatSupport.java index cd6c5bb9a..fc2fc1c22 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/WeatherNeatSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/WeatherNeatSupport.java @@ -51,6 +51,6 @@ public class WeatherNeatSupport { public GBDeviceEvent[] handleMessage(ArrayList> pairs) { GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); sendBytes.encodedBytes = encodeWeatherNeatMessage("Berlin", "22 C", "cloudy", 0); - return new GBDeviceEvent[] {sendBytes}; + return new GBDeviceEvent[]{sendBytes}; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java index e509dca6b..8a521679a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/receivers/GBMusicControlReceiver.java @@ -3,8 +3,10 @@ package nodomain.freeyourgadget.gadgetbridge.service.receivers; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.media.AudioManager; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.view.KeyEvent; import org.slf4j.Logger; @@ -51,16 +53,25 @@ public class GBMusicControlReceiver extends BroadcastReceiver { } if (keyCode != -1) { + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context); + String audioPlayer = sharedPrefs.getString("audio_player", "default"); + long eventtime = SystemClock.uptimeMillis(); Intent downIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); KeyEvent downEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_DOWN, keyCode, 0); downIntent.putExtra(Intent.EXTRA_KEY_EVENT, downEvent); + if (!"default".equals(audioPlayer)) { + downIntent.setPackage(audioPlayer); + } context.sendOrderedBroadcast(downIntent, null); Intent upIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); KeyEvent upEvent = new KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, keyCode, 0); upIntent.putExtra(Intent.EXTRA_KEY_EVENT, upEvent); + if (audioPlayer != null) { + upIntent.setPackage(audioPlayer); + } context.sendOrderedBroadcast(upIntent, null); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index 819a187e3..eaea3c84d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -5,22 +5,23 @@ import org.slf4j.LoggerFactory; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; /** * An abstract base class for devices speaking a serial protocol, like via * an rfcomm bluetooth socket or a TCP socket. - * + *

    * This class uses two helper classes to deal with that: * - GBDeviceIoThread, which creates and maintains the actual socket connection and implements the transport layer * - GBDeviceProtocol, which implements the encoding and decoding of messages, i.e. the actual device specific protocol - * + *

    * Note that these two classes need to be implemented in a device specific way. - * + *

    * This implementation implements all methods of {@link EventHandler}, calls the {@link GBDeviceProtocol device protocol} * to create the device specific message for the respective events and sends them to the device via {@link #sendToDevice(byte[])}. */ @@ -84,6 +85,7 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport /** * Sends the given message to the device. This implementation delegates the * writing to the {@link #getDeviceIOThread device io thread} + * * @param bytes the message to send to the device */ protected void sendToDevice(byte[] bytes) { @@ -106,20 +108,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport } @Override - public void onSMS(String from, String body) { - byte[] bytes = gbDeviceProtocol.encodeSMS(from, body); - sendToDevice(bytes); - } - - @Override - public void onEmail(String from, String subject, String body) { - byte[] bytes = gbDeviceProtocol.encodeEmail(from, subject, body); - sendToDevice(bytes); - } - - @Override - public void onGenericNotification(String title, String details, int handle) { - byte[] bytes = gbDeviceProtocol.encodeGenericNotification(title, details, handle); + public void onNotification(NotificationSpec notificationSpec) { + byte[] bytes = gbDeviceProtocol.encodeNotification(notificationSpec); sendToDevice(bytes); } @@ -148,8 +138,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport } @Override - public void onAppStart(UUID uuid) { - byte[] bytes = gbDeviceProtocol.encodeAppStart(uuid); + public void onAppStart(UUID uuid, boolean start) { + byte[] bytes = gbDeviceProtocol.encodeAppStart(uuid, start); sendToDevice(bytes); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index 22c23063f..439c75290 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -2,20 +2,13 @@ package nodomain.freeyourgadget.gadgetbridge.service.serial; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand; public abstract class GBDeviceProtocol { - public byte[] encodeSMS(String from, String body) { - return null; - } - - public byte[] encodeEmail(String from, String subject, String body) { - return null; - } - - public byte[] encodeGenericNotification(String title, String details, int handle) { + public byte[] encodeNotification(NotificationSpec notificationSpec) { return null; } @@ -47,7 +40,7 @@ public abstract class GBDeviceProtocol { return null; } - public byte[] encodeAppStart(UUID uuid) { + public byte[] encodeAppStart(UUID uuid, boolean start) { return null; } @@ -70,5 +63,4 @@ public abstract class GBDeviceProtocol { public GBDeviceEvent[] decodeResponse(byte[] responseData) { return null; } - -} \ No newline at end of file +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 960cb5c40..c2ebea511 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -5,10 +5,10 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; public class DeviceHelper { private static DeviceHelper instance = new DeviceHelper(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java index b2631f13b..e9c1f396e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java @@ -51,7 +51,8 @@ public class FileUtils { * Reads the contents of the given InputStream into a byte array, but does not * read more than maxLen bytes. If the stream provides more than maxLen bytes, * an IOException is thrown. - * @param in the stream to read from + * + * @param in the stream to read from * @param maxLen the maximum number of bytes to read/return * @return the bytes read from the InputStream * @throws IOException when reading failed or when maxLen was exceeded diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java index 0108af857..8660f9977 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java @@ -40,6 +40,7 @@ public class GB { public static final int NOTIFICATION_ID = 1; public static final int NOTIFICATION_ID_INSTALL = 2; public static final int NOTIFICATION_ID_LOW_BATTERY = 3; + public static final int NOTIFICATION_ID_TRANSFER = 4; private static final Logger LOG = LoggerFactory.getLogger(GB.class); public static final int INFO = 1; @@ -80,6 +81,11 @@ public class GB { nm.notify(id, notification); } + private static void removeNotification(int id, Context context) { + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(id); + } + public static void setReceiversEnableState(boolean enable, Context context) { LOG.info("Setting broadcast receivers to: " + enable); final Class[] receiverClasses = { @@ -275,6 +281,41 @@ public class GB { } } + private static Notification createTransferNotification(String text, boolean ongoing, + int percentage, Context context) { + Intent notificationIntent = new Intent(context, ControlCenter.class); + notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, + notificationIntent, 0); + + NotificationCompat.Builder nb = new NotificationCompat.Builder(context) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setContentTitle(context.getString(R.string.app_name)) + .setContentText(text) + .setContentIntent(pendingIntent) + .setOngoing(ongoing); + + if (ongoing) { + nb.setProgress(100, percentage, percentage == 0); + nb.setSmallIcon(android.R.drawable.stat_sys_download); + } else { + nb.setProgress(0, 0, false); + nb.setSmallIcon(android.R.drawable.stat_sys_download_done); + } + + return nb.build(); + } + + public static void updateTransferNotification(String text, boolean ongoing, int percentage, Context context) { + if (percentage == 100) { + removeNotification(NOTIFICATION_ID_TRANSFER, context); + } else { + Notification notification = createTransferNotification(text, ongoing, percentage, context); + updateNotification(notification, NOTIFICATION_ID_TRANSFER, context); + } + } + private static Notification createInstallNotification(String text, boolean ongoing, int percentage, Context context) { Intent notificationIntent = new Intent(context, ControlCenter.class); @@ -314,7 +355,7 @@ public class GB { notificationIntent, 0); NotificationCompat.Builder nb = new NotificationCompat.Builder(context) - .setContentTitle( context.getString(R.string.notif_battery_low_title)) + .setContentTitle(context.getString(R.string.notif_battery_low_title)) .setContentText(text) .setContentIntent(pendingIntent) .setSmallIcon(R.drawable.ic_notification_low_battery) @@ -339,4 +380,10 @@ public class GB { public static GBEnvironment env() { return environment; } + + public static void assertThat(boolean condition, String errorMessage) { + if (!condition) { + throw new AssertionError(errorMessage); + } + } } diff --git a/app/src/main/res/layout-land/fragment_sleepchart.xml b/app/src/main/res/layout-land/fragment_sleepchart.xml index 0323f2c52..5b4e0b409 100644 --- a/app/src/main/res/layout-land/fragment_sleepchart.xml +++ b/app/src/main/res/layout-land/fragment_sleepchart.xml @@ -11,10 +11,10 @@ android:layout_weight="40"> - diff --git a/app/src/main/res/layout/activity_appblacklist.xml b/app/src/main/res/layout/activity_appblacklist.xml new file mode 100644 index 000000000..831675a38 --- /dev/null +++ b/app/src/main/res/layout/activity_appblacklist.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_charts.xml b/app/src/main/res/layout/fragment_charts.xml index 9501bcd34..bd8983f8e 100644 --- a/app/src/main/res/layout/fragment_charts.xml +++ b/app/src/main/res/layout/fragment_charts.xml @@ -3,7 +3,7 @@ android:layout_height="match_parent" tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity$PlaceholderFragment"> - - diff --git a/app/src/main/res/layout/item_with_checkbox.xml b/app/src/main/res/layout/item_with_checkbox.xml new file mode 100644 index 000000000..c166b0aa6 --- /dev/null +++ b/app/src/main/res/layout/item_with_checkbox.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 77d54c80e..56536ab62 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 + FW/App Installer Es soll die Firmware %s anstelle der aktuell installierten Version aufs Mi Band gespielt werden. @@ -24,13 +25,15 @@ Einstellungen Allgemeine Einstellungen Verbinde, wenn Bluetooth eingeschaltet wird + Bevorzugter Audioplayer + Standard Datum und Zeit Uhrzeit synchronisieren Synchronisiere die Urzeit mit dem Gerät (bei Verbindingsaufbau und wenn die Zeit oder Zeitzone auf dem Android Gerät eingestellt wird) Benachrichtigungen Wiederholungen + Anrufe SMS - Eingehende Anrufe K9-Mail Pebble Nachrichten Unterstützung für Anwendungen, die Benachrichtigungen per Intent an die Pebble schicken. Kann für Conversations verwendet werden. @@ -42,10 +45,13 @@ Entwickleroptionen Mi Band MAC Adresse Pebble Einstellungen + Erlaube Zugriff von anderen Android Apps + Experimentelle Unterstützung für Android Apps, die PebbleKit benutzen Benachrichtigungsprotokoll erzwingen Diese Option erzwingt das neuste Benachrichtigungsprotokoll abhängig von der Firmwareversion. NUR EINSCHALTEN, WENN DU WEISST WAS DU TUST! Ungetestete Features freischalten Schaltet ungetetestete Features frei. TU DIES NUR, WENN DU WEIßT, WAS DU TUST! + Neuverbindungsversuche nicht verbunden verbinde verbunden @@ -144,7 +150,7 @@ Deine Aktivität (ALPHA) Kann keine Verbindung herstellen: %1$s Kann keinen Handler für die Installation dieser Datei finden. - Kann die gegebene Datei %1$s nicht installieren + Konnte folgende Datei nicht installieren: %1$s Kann die gegebene Firmware nicht installieren. Sie passt nicht zur Hardware Revision der Pebble. Bitte warten während der Installationsoption festgestellt wird... Gadget Akkustand niedrig! @@ -172,4 +178,6 @@ Schritte Live Aktivität Schritte heute, Ziel: %1$s + Transfer von Aktivitätsdaten nicht bestätigen + Wenn der Transfer der Aktivitätsdaten nicht bestätigt wird, werden die Daten nicht auf dem Mi Band gelöscht. Das ist Sinnvoll, wenn neben Gadgetbridge noch andere Apps auf das Mi Band zugreifen. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d888ee7da..5b2d75e58 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -14,6 +14,8 @@ Gestor de App Borrar + + Lista negra de notificaciones Instalador de FW/App Estás a punto de instalar el firmware %s en lugar del que está en tu MiBand. @@ -24,13 +26,14 @@ Ajustes Ajustes generales Conectar al encender el bluetooth + Reproductor de audio preferido + Predeterminado Fecha y hora Sincronizar hora Sincroniza la hora en el dispositivo cuando se conecte o se cambie la hora o zona horaria en Android Notificaciones Repeticiones SMS - Llamadas entrantes K9-Mail Mensajes de Pebble Soporte para aplicaciones que envían notificaciones a Pebble a través de Intent. Puede ser usado por Conversations. @@ -39,6 +42,7 @@ siempre cuando la pantalla está apagada nunca + Añadir app a la lista negra Opciones de desarrollador Dirección de MiBand Ajustes de Pebble @@ -46,6 +50,7 @@ Esta opción fuerza el uso del último protocolo de notificación dependiendo de la versión de firmware. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO! Habilitar características no probadas Habilita características que no han sido comprobadas. ¡HABILÍTALO SOLO SI SABES LO QUE ESTÁS HACIENDO! + Intentos de reconectar no conectado conectando conectado @@ -144,7 +149,7 @@ Tu actividad (ALPHA) No se puede conectar: %1$s No se ha podido encontrar un controlador para instalar este archivo. - No se puede instalar el archivo: %1$s + No se ha podido instalar el fichero: %1$s No se puede instalar este firmware: no coincide con la revision hardware de tu Pebble. Por favor, espera mientras se determina el estado de la instalación... Batería baja del Gadget! @@ -172,4 +177,6 @@ Pasos Actividad Pasos hoy, objetivo: %1$s + No \"ackear\" transferencia de datos de actividad + Si los datos de actividad no son \"ackeados\" a la banda, no serán despejados. Útil si GB se usa conjuntamente con otras apps. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e971ee2f0..aa95ffc47 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -13,10 +13,11 @@ Gestionnaire d\'application Supprimer + Vous êtes sur le point d\'installer le firmware %s à la place de celui qui est actuellement sur votre Mi Band. Ce firmware a été testé et est connu pour être compatible avec Gadgetbridge. - Ce firmware n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge. \ N \ nIl n\'est pas conseillé de flasher votre Mi Band. + Ce firmware n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge. \n \n Il n\'est pas conseillé de flasher votre Mi Band. Paramètre Paramètres généraux @@ -27,26 +28,25 @@ Notifications Répétitions SMS - Appel entrant K9-Email Message Pebble Support pour les applications qui envoient des notifications au Pebble via Intent. Peut être utilisé pour les conversations. Support des notififactions génériques - même lorsque l\'écran est allumé - toujours - quand l\'écran est éteint - jamais + Même lorsque l\'écran est allumé + Toujours + Quand l\'écran est éteint + Jamais Options développeur Adresse Mi Band Paramètres Pebble Protocole des notifications en vigueur - Cette option force l\'utilisant du dernier protocole de notification qui dépend de la verrsion du firmware. ACTIVER LA UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES! - Activités les fonctionnalités non testé - Activer les fonctionnalités non testés - non connecté - en train de se connecter - connecté - état inconnu + Cette option force l\'utilisation du dernier protocole de notification qui dépend de la verrsion du firmware. ACTIVER LA UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES! + Activer les fonctionnalités non testé + Activer les fonctionnalités non testés. ACTIVER UNIQUEMENT SI VOUS SAVEZ CE QE VOUS FAITES! + Non connecté + En train de se connecter + Connecté + État inconnu HW: %1$s FW: %2$s FW: %1$s (inconnu) @@ -55,21 +55,22 @@ Ceci est un test de notification venant de Gadgetbridge Le bluetooth n\'est pas supporté Le bluetooth est désactivé - Taper sur lpour le connecter au gestionnaire d\'application + Taper sur l\'appareil pour le connecter au gestionnaire d\'application Tapper sur l\'appareil pour le connecter Ne peut être connecter. L’adresse bluetooth est invalide? Gadgetbridge est en fonctionnement Installation du binaire %1$d/%2$d - installation en échec - installation réalisé + Echec d\'installation + Installation réalisé VOUS ÊTES EN TRAIN D\'INSTALLER UN FIRMWARE, PROCEDEZ À VOS PROPRES RISQUES. CE FIRMWARE EST POUR LA VERSION HW: %s Vous êtes sur le point d\'installer l\'application suivante:\n\n\n%1$s Version %2$s par %3$s\n N/A - initialisé + Initialisé %1$s par %2$s Décourir les appareils Arreter de scanner Démarrer le scan + Connecter un nouvelle appareil %1$s (%2$s) Coupler l\'appareil Utiliser l\'appareillage bluetouth d\'android pour coupler l\'appareil @@ -78,11 +79,11 @@ Aucune adresse mac fournis, ne peut être couplé Paramètres spécifique à l\'appareil Paramètres Mi Band - homme - femme - autre - gauche - droite + Homme + Femme + Autre + Gauche + Droite Aucune donnée utilisateur valide fournis, utilisation des données fictives pour le moment Quand votre Mi Band vibre et clignote, appuyez dessus plusieurs fois d\'affilée. Installer @@ -98,7 +99,7 @@ Nombre de vibration Moniteur de sommeil Ecrire le fichier de logs (besoin de redemarrer) - iniitialisation + Initialisation Récupération des données d\'activité De %1$s à %2$s Port main gauche ou droite? @@ -130,17 +131,16 @@ Jeu Ven Sam - réveil intelligent + Réveil intelligent Il y avais une erreur lors du paramétrage des alarmes, s\'il vous plaît essayer à nouveau! Alarmes envoyer à l\'appareil Aucune donnée. Synchroniser l\'appareil? - A propos du transférer %1$s de données à partir de %2$s + À propos du transférer %1$s de données à partir de %2$s Objectif de pas par jour Erreur d’exécution %1$s\' Votre activité (ALPHA) Impossible de se connecter: %1$s Impossible de trouver un gestionnaire pour installer ce fichier. - Impossible d\'installer le fichier donné: %1$s Impossible d\'installer le firmware donnée: il ne correspond pas à la version du matériel de votre Pebble. S\'il vous plait patientez pendant la détermination du status de l\'installation... Gadget batterie Vide! @@ -160,9 +160,10 @@ Révision correcte du matériel Version Hardware incorrecte! %1$s (%2$s) - Problème avec le transfert du firmware. Ne redémarrez pas votre bande Mi! + Problème avec le transfert du firmware. Ne redémarrez pas votre Mi band! Problème avec le transfert de métadonnées du firmware Installation complète du firmware Installation complète du firmware, redémarrage de l\'appareil Échec lors de l\'écriture du firmware + Pas diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b71c7f161..68f23ad27 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -7,13 +7,21 @@ Esci Sincronizza Monitoraggio del sonno (ALPHA) + Trova dispositivo smarrito... Salva uno screenshot Disconetti Debug Gestione app Cancella + + Blocco notifiche + Installazione FW/App + Il firmware %s verrà installato al posto di quello attualmente sulla Mi Band. + Questo firmware è stato testato ed è compatibile con Gadgetbridge. + Questo firmware NON è stato testato e potrebbe non essere compatibile con Gadgetbridge.\n\nInstallarlo a proprio rischio sulla Mi Band. + Se hai intenzione di procedere e tutto continua a funzionare ti invitiamo a contattare gli sviluppatori e dire loro di aggiungere il firmware: %s a quelli testati Impostazioni Impostazioni globali @@ -24,7 +32,6 @@ Notifiche Ripetizioni SMS - Chiamate in arrivo K9-Mail Pebble Messages Supporto per applicazioni che inviano le notifiche a Pebble usando Intents. Può essere usato per Conversations. @@ -33,11 +40,14 @@ sempre se lo schermo è spento mai + Blocca applicazioni Opzioni di sviluppo Indirizzo Miband Impostazioni Pebble Forza protocollo delle notifiche 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! + Abilita funzionalità non testate + Abilita funzionalità non testate. ABILITARE SOLO SE SI SA QUELLO CHE SI STA FACENDO! non connesso in collegamento connesso @@ -57,6 +67,7 @@ installazione del binario %1$d/%2$d Installazione fallita! installazione conclusa con successo + STAI INSTALLANDO UN FIRMWARE, PROCEDI A TUO RISCHIO.\n\n Questo firmware è per la versione HW: %s Si sta per installare la app:\n\n\n%1$s Versione %2$s di %3$s\n N/A inizializzato @@ -64,6 +75,7 @@ Ricerca dispositivi Interrompi scansione Inizia scansione + Connetti un nuovo dispositivo %1$s (%2$s) Accoppia dispositivo Utilizza la funzione del sistema operativo per accoppiare il dispositivo. @@ -124,9 +136,44 @@ Gio Ven Sab - Risveglio intelligente + Sveglia intelligente C\'è stato un errore impostando le sveglie, prova di nuovo. Sveglie sincronizzate con il dispositivo! Non ci sono dati. Effettua una sincronizzazione con il tuo device. Vengono trasferiti %1$s a partire dal %2$s + Traguardo giornaliero di passi + Errore eseguendo \'%1$s\' + Le tue attività (ALPHA) + Impossibile connettere: %1$s + Impossibile aprire questo file. + Impossibile installare il file: %1$s + Impossibile installare il firmware: non corrisponde alla versione hardware del tuo Pebble. + Lo stato dell\'installazione viene rilevato... + Batteria del dispositivo bassa! + %1$s batteria rimanente: %2$s%% + Ultima ricarica: %s \n + Numero di ricariche: %s + Il tuo sonno + Passi della settimana + Attività e sonno + Aggiornamento del Firmware... + Il file non può essere installato, il dispositivo non è pronto. + Firmware Mi Band: %1$s + Versione compatibile + Versione non testata! + Connessione al device: %1$s + Firmware Pebble: %1$s + Versione hardware corretta + Conflitto nella versione hardware! + %1$s (%2$s) + Problema durante il caricamento del firmware. NON RIAVVIARE la Mi Band! + Problema durante il caricamento dei metadati del firmware + Installazione del firmware completata + Installazione del firmware completata, riavvio del dispositivo... + Scrittura del firmware non riuscita + 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. diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3a5b0e913..859e57704 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -12,6 +12,7 @@ 앱 관리자 삭제 + 설정 @@ -23,7 +24,6 @@ 알림 반복 SMS - 걸려오는 전화 K-9 메일 Pebble 메세지 인텐트를 통해 Pebble에 알림을 전송할 수 있는 앱 지원. Conversations 앱 등에 사용할 수 있습니다. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 7c0c35c35..5aa983962 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -5,25 +5,36 @@ Настройки Отладка Выход + Синхронизировать Анализ сна (АЛЬФА) + Найти устройство... Сделать снимок экрана Отключиться Отладка App Mananger Удалить + + Заблокированные уведомления + Установщик прошивки/приложений + Вы собираетесь установить прошивку %s заместо текущей на вашем Mi Band. + Эта прошивка была проверена и совместима с Gadgetbridge. + Эта прошивка не протестирована и может быть несовместима с Gadgetbridge.\n\nНе рекомендуется устанавливать её на ваш Mi Band! + Если вы желаете продолжить и впоследствии всё функционирует нормально, пожалуйста, сообщите разработчикам Gadgetbridge, чтобы они пометили как совместимую версию прошивки: %s Настройки Общие настройки Подключение к устройству при активации Bluetooth + Предпочтительный музыкальный плеер + По-умолчанию Дата и время Синхронизировать время при подключении Синхронизировать время при подключении к устройству, а также при изменении времени или временной зоны в системе Уведомления Повторы + Вызовы СМС-сообщения - Входящие звонки K9-Mail Сообщения Pebble Поддержка приложений, посылающих уведомления на Pebble с помощью Intent. Может использоваться для Conversations. @@ -32,11 +43,17 @@ всегда когда экран выключен никогда + Нежелательные приложения Настройки для разработчиков Адрес Mi Band Настройки Pebble + Разрешить доступ приложениям третьих лиц + Добавить экспериментальную поддержку приложений Android, использующих PebbleKit Принудительный протокол уведомлений Эта настройка заставляет принудительно использовать самый новый протокол уведомлений, зависящий от версии прошивки. ВКЛЮЧАЙТЕ, ТОЛЬКО ЕСЛИ ЗНАЕТЕ НА ЧТО ВЫ ИДЁТЕ. + Включить непроверенные функции + Включить функции, которые не протестированы. ВКЛЮЧАЙТЕ НА СВОЙ СТРАХ И РИСК! + Попытки переподключения нет соединения соединение соединено @@ -56,6 +73,7 @@ установки бинарного файла %1$d/%2$d установка не удалась! установка завершена + ВЫ ПЫТАЕТЕСЬ УСТАНОВИТЬ ПРОШИВКУ, ПРОДОЛЖАЙТЕ НА СВОЙ СТРАХ И РИСК.\n\n\n Эта прошивка для ревизии устройства: %s Вы собираетесь установить приложение:\n\n\n%1$s версия %2$s от %3$s\n Недоступно Инициализировано @@ -63,6 +81,7 @@ Поиск устройства Остановить поиск Умный поиск + Подключиться к новому устройству %1$s (%2$s) Сопряжение устройств Для сопряжения устройств используйте диалог Android. @@ -112,6 +131,7 @@ Уведомления о входящем звонке Найти потерянное устройство Отмените, чтобы остановить вибро + Ваша активность Завести Будильник Завести будильник Свойства будильника @@ -126,4 +146,40 @@ Произошла ошибка при настройке будильника, попробуйте ещё! Будильник был послан на устройство! Нет данных. Синхронизировать устройство? + Будет передано %1$s данных, начиная с %2$s + Цель шагов на каждый день + Произошла ошибка при выполнении \'%1$s\' + Ваша активность (АЛЬФА) + Подключиться не удалось: %1$s + Не удалось найти обработчик для установки этого файла. + Не удалось установить данный файл: %1$s + Не удалось установить данную прошивку: она не совпадает с версией устройства вашего Pebble + Пожалуйста подождите, идёт определение статуса установки... + Низкий заряд устройства! + %1$s осталось заряда: %2$s%% + Последний раз устройство заряжалось: %s \n + Количество циклов перезарядки: %s + Ваш сон + Шагов в неделю + Ваша активность и сон + Обновление прошивки... + Файл не может быть установлен, устройство не готово. + Версия прошивки Mi Band: %1$s + Совместимая версия + Не протестированная версия! + Подключение к устройству: %1$s + Версия прошивки Pebble: %1$s + Корректная ревизия устройства + Ревизия устройства не совпадает! + %1$s (%2$s) + Проблема с передачей прошивки. НЕ ПЕРЕЗАГРУЖАЙТЕ ваш Mi Band! + Проблема с передачей метаданных прошивки + Установка прошивки завершена + Установка прошивки завершена, устройство перезагружается... + Запись прошивки завершилась неудачей + Шаги + Жизненная активность + Шагов сегодня, цель: %1$s + Не передавать данные об активности + Если данные об активности не будут переданы на устройство, оно не будет очищено. Полезно, если GB используется с другими приложениями. diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 88b4f5f34..c18572139 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -11,6 +11,15 @@ never + + @string/always + @string/never + + + always + never + + @string/male @string/female diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 781c6a4f6..cbba2238a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,9 @@ App Manager Delete + + Notification Blacklist + FW/App installer You are about to install firmware %s instead of the one currently on your Mi Band. @@ -30,15 +33,16 @@ General Settings Connect to device when Bluetooth turned on - + Preferred Audioplayer + Default Date and Time Sync time Sync time to device when connecting and when time or timezone changes on Android Notifications Repetitions + Phone Calls SMS - Incoming Calls K9-Mail Pebble Messages Support for applications which send Notifications to the Pebble via Intent. Can be used for Conversations. @@ -49,15 +53,19 @@ when screen is off never + Blacklist Apps + Developer Options Mi Band address Pebble Settings + Allow 3rd Party Android App Access + Enable experimental support for Android Apps using PebbleKit Force Notification Protocol This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING! Enable untested features Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING! - + Reconnection Attempts not connected connecting connected diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6dfe88c31..599e88b4c 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -7,6 +7,10 @@ android:defaultValue="false" android:key="general_autoconnectonbluetooth" android:title="@string/pref_title_general_autoconnectonbluetooth" /> + + + + + android:key="pref_key_pebble" + android:title="@string/pref_title_pebble_settings"> + + + + + android:summary="@string/pref_summary_pebble_forceprotocol" + android:title="@string/pref_title_pebble_forceprotocol" /> + android:summary="@string/pref_summary_pebble_forceuntested" + android:title="@string/pref_title_pebble_forceuntested" />