diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bf3ae12fb..0d6b1836f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -413,6 +413,11 @@
android:authorities="com.getpebble.android.provider"
android:exported="true" />
+
+
. */
+package nodomain.freeyourgadget.gadgetbridge.contentprovider;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE;
+
+/**
+ * A content Provider, which publishes read only RAW @see ActivitySample to other applications
+ *
+ * TODO:
+ * For this to work there must be an additional api which switches the GadgetBridge to "start activity"
+ */
+public class HRContentProvider extends ContentProvider {
+
+ private static final int DEVICES_LIST = 1;
+ private static final int REALTIME = 2;
+ private static final int ACTIVITY_START = 3;
+ private static final int ACTIVITY_STOP = 4;
+
+ private static final UriMatcher URI_MATCHER;
+
+ static {
+ URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ URI_MATCHER.addURI(HRContentProvider.AUTHORITY,
+ "devices", DEVICES_LIST);
+ URI_MATCHER.addURI(HRContentProvider.AUTHORITY,
+ "realtime", REALTIME);
+ URI_MATCHER.addURI(HRContentProvider.AUTHORITY,
+ "activity_start", ACTIVITY_START);
+ URI_MATCHER.addURI(HRContentProvider.AUTHORITY,
+ "activity_stop", ACTIVITY_STOP);
+ }
+
+ private ActivitySample buffered_sample = null;
+
+ // this is only needed for the MatrixCursor constructor
+ public static final String[] deviceColumnNames = new String[]{"Name", "Model", "Address"};
+ public static final String[] activityColumnNames = new String[]{"Status", "Message"};
+ public static final String[] realtimeColumnNames = new String[]{"Status", "Message"};
+
+ static final String AUTHORITY = "com.gadgetbridge.heartrate.provider";
+ static final String ACTIVITY_START_URL = "content://" + AUTHORITY + "/activity_start";
+ static final String ACTIVITY_STOP_URL = "content://" + AUTHORITY + "/activity_stop";
+
+ static final String REALTIME_URL = "content://" + AUTHORITY + "/realtime";
+ static final String DEVICES_URL = "content://" + AUTHORITY + "/devices";
+
+ public static final Uri ACTIVITY_START_URI = Uri.parse(ACTIVITY_START_URL);
+ public static final Uri ACTIVITY_STOP_URI = Uri.parse(ACTIVITY_STOP_URL);
+ public static final Uri REALTIME_URI = Uri.parse(REALTIME_URL);
+ public static final Uri DEVICES_URI = Uri.parse(DEVICES_URL);
+
+ private GBDevice mGBDevice = null;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.e(HRContentProvider.class.getName(), "Received Event, aciton: " + action);
+
+ switch (action) {
+ case GBDevice.ACTION_DEVICE_CHANGED:
+ mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
+ break;
+ case DeviceService.ACTION_REALTIME_SAMPLES:
+ buffered_sample = (ActivitySample) intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE);
+ // This notifies the observer
+ getContext().
+ getContentResolver().
+ notifyChange(REALTIME_URI, null);
+ break;
+ default:
+ break;
+ }
+
+ }
+ };
+
+ @Override
+ public boolean onCreate() {
+ Log.i(HRContentProvider.class.getName(), "Creating...");
+ IntentFilter filterLocal = new IntentFilter();
+
+ filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+ filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
+
+ LocalBroadcastManager.getInstance(this.getContext()).registerReceiver(mReceiver, filterLocal);
+
+ //TODO Do i need only for testing or also in production?
+ this.getContext().registerReceiver(mReceiver, filterLocal);
+
+ return true;
+ }
+
+ @Override
+ public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ Log.e(HRContentProvider.class.getName(), "query uri " + uri.toString());
+ MatrixCursor mc;
+ Intent intent;
+
+ switch (URI_MATCHER.match(uri)) {
+ case DEVICES_LIST:
+ DeviceManager deviceManager = ((GBApplication) GBApplication.getContext()).getDeviceManager();
+ List l = deviceManager.getDevices();
+ Log.i(HRContentProvider.class.getName(), String.format("listing %d devices", l.size()));
+
+ mc = new MatrixCursor(deviceColumnNames);
+ for (GBDevice dev : l) {
+ mc.addRow(new Object[]{dev.getName(), dev.getModel(), dev.getAddress()});
+ }
+ return mc;
+ case ACTIVITY_START:
+ Log.i(HRContentProvider.class.getName(), String.format("Get ACTIVTY START"));
+ intent =
+ new Intent()
+ .setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
+ .putExtra(EXTRA_BOOLEAN_ENABLE, true);
+
+ GBApplication.getContext().startService(intent);
+ mc = new MatrixCursor(activityColumnNames);
+ mc.addRow(new String[]{"OK", "No error"});
+ return mc;
+ case ACTIVITY_STOP:
+ Log.i(HRContentProvider.class.getName(), String.format("Get ACTIVITY STOP"));
+ intent =
+ new Intent()
+ .setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
+ .putExtra(EXTRA_BOOLEAN_ENABLE, false);
+
+ GBApplication.getContext().startService(intent);
+ mc = new MatrixCursor(activityColumnNames);
+ mc.addRow(new String[]{"OK", "No error"});
+ return mc;
+ case REALTIME:
+ String sample_string = (buffered_sample == null) ? "" : buffered_sample.toString();
+ Log.e(HRContentProvider.class.getName(), String.format("Get REALTIME buffered sample %s", sample_string));
+ mc = new MatrixCursor(realtimeColumnNames);
+
+ // TODO no strings...
+ mc.addRow(new Object[]{"OK", sample_string, buffered_sample});
+ return mc;
+ }
+ return new MatrixCursor(deviceColumnNames);
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ Log.e(HRContentProvider.class.getName(), "getType uri " + uri);
+ return null;
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, ContentValues values, String selection, String[]
+ selectionArgs) {
+ return 0;
+ }
+
+ // Das ist eine debugging funktion
+ @Override
+ public void shutdown() {
+ LocalBroadcastManager.getInstance(this.getContext()).unregisterReceiver(mReceiver);
+ super.shutdown();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java
index 77a8bb1bb..736034227 100644
--- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/database/SampleProviderTest.java
@@ -1,10 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.database;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import org.apache.tools.ant.types.resources.comparators.Content;
import org.junit.Test;
import java.util.List;
+import nodomain.freeyourgadget.gadgetbridge.contentprovider.HRContentProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
@@ -12,21 +26,37 @@ import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.test.TestBase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.robolectric.Shadows.shadowOf;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowContentResolver;
+
public class SampleProviderTest extends TestBase {
private GBDevice dummyGBDevice;
+ private ContentResolver mContentResolver;
@Override
public void setUp() throws Exception {
super.setUp();
dummyGBDevice = createDummyGDevice("00:00:00:00:10");
+
+ mContentResolver = app.getContentResolver();
+
+ HRContentProvider provider = new HRContentProvider();
+ // Stuff context into provider
+ provider.attachInfo(app.getApplicationContext(), null);
+
+ ShadowContentResolver.registerProviderInternal("com.gadgetbridge.heartrate.provider", provider);
}
@Test
@@ -122,7 +152,7 @@ public class SampleProviderTest extends TestBase {
MiBandActivitySample s3 = createSample(sampleProvider, MiBandSampleProvider.TYPE_DEEP_SLEEP, 1200, 10, 62, 4030, user, device);
MiBandActivitySample s4 = createSample(sampleProvider, MiBandSampleProvider.TYPE_LIGHT_SLEEP, 2000, 10, 60, 4030, user, device);
- sampleProvider.addGBActivitySamples(new MiBandActivitySample[] { s3, s4 });
+ sampleProvider.addGBActivitySamples(new MiBandActivitySample[]{s3, s4});
// first checks for irrelevant timestamps => no samples
List samples = sampleProvider.getAllActivitySamples(0, 0);
@@ -170,4 +200,127 @@ public class SampleProviderTest extends TestBase {
sleepSamples = sampleProvider.getSleepSamples(1500, 2500);
assertEquals(1, sleepSamples.size());
}
+
+ private void generateSampleStream(MiBandSampleProvider sampleProvider) {
+ final User user = DBHelper.getUser(daoSession);
+ final Device device = DBHelper.getDevice(dummyGBDevice, daoSession);
+
+ for (int i = 0; i < 10; i++) {
+ MiBandActivitySample sample = createSample(sampleProvider, MiBandSampleProvider.TYPE_ACTIVITY, 100 + i * 50, 10, 60 + i * 5, 1000 * i, user, device);
+ Log.d(SampleProviderTest.class.getName(), "Sending sample " + sample.toString());
+ Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
+ .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
+ app.sendBroadcast(intent);
+ }
+ }
+
+
+ @Test
+ public void testContentProvider() {
+ dummyGBDevice.setState(GBDevice.State.CONNECTED);
+ final MiBandSampleProvider sampleProvider = new MiBandSampleProvider(dummyGBDevice, daoSession);
+
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app);
+ sharedPreferences.edit().putString(MiBandConst.PREF_MIBAND_ADDRESS, dummyGBDevice.getAddress()).commit();
+
+ // Refresh the device list
+ dummyGBDevice.sendDeviceUpdateIntent(app);
+
+ /*
+ * (DeviceSupportFactory)mFactory.createDeviceSupport(gbDevice); -->
+ * deviceSupport = new ServiceDeviceSupport(new MiBand2Support(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
+ *
+ */
+
+ /*
+ * Test the device uri
+ */
+ Cursor cursor = mContentResolver.query(HRContentProvider.DEVICES_URI, null, null, null, null);
+
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+
+ if (cursor.moveToFirst()) {
+ do {
+ String deviceName = cursor.getString(0);
+ String deviceAddress = cursor.getString(2);
+
+ assertEquals(dummyGBDevice.getName(), deviceName);
+ assertEquals(dummyGBDevice.getAddress(), deviceAddress);
+ } while (cursor.moveToNext());
+ }
+
+ /*
+ * Test the activity start uri
+ */
+ cursor = mContentResolver.query(HRContentProvider.ACTIVITY_START_URI, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ do {
+ String status = cursor.getString(0);
+ String message = cursor.getString(1);
+ assertEquals("OK", status);
+ assertEquals("No error", message);
+
+ } while (cursor.moveToNext());
+ }
+
+ /*
+ * Test the activity start uri
+ */
+ cursor = mContentResolver.query(HRContentProvider.ACTIVITY_STOP_URI, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ do {
+ String status = cursor.getString(0);
+ String message = cursor.getString(1);
+ assertEquals("OK", status);
+ assertEquals("No error", message);
+ } while (cursor.moveToNext());
+ }
+
+
+ /*
+ * Test realtime data and content observers
+ */
+ class A1 extends ContentObserver {
+ public int numObserved = 0;
+ A1() {
+ super(null);
+ }
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ Log.e(SampleProviderTest.class.getName(), "Changed " + uri.toString());
+ numObserved++;
+ }
+ };
+ A1 a1 = new A1();
+
+ mContentResolver.registerContentObserver(HRContentProvider.REALTIME_URI, false, a1);
+ generateSampleStream(sampleProvider);
+
+ /*
+ * Test the realtime data start uri
+ */
+ cursor = mContentResolver.query(HRContentProvider.REALTIME_URI, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ do {
+ String status = cursor.getString(0);
+ String message = cursor.getString(1);
+ assertEquals("OK", status);
+ assertEquals(false, message.isEmpty());
+ Log.i("test", "------FOUND SOMETHING------");
+ } while (cursor.moveToNext());
+ }
+
+ List l = shadowOf(RuntimeEnvironment.application).getBroadcastIntents();
+ assertEquals(10, l.size());
+ for (Intent i : l)
+ assertEquals(i.getAction(), DeviceService.ACTION_REALTIME_SAMPLES);
+
+ List r = shadowOf(RuntimeEnvironment.application).getReceiversForIntent(l.get(0));
+ assertEquals(1, r.size());
+
+ assertEquals(a1.numObserved, 10);
+
+ }
}