[WIP] Introduce a ContentProvider for HR Data

This commit is contained in:
Benedikt Elser 2018-07-04 13:50:53 +02:00
parent 86038a7abf
commit 1d1cd006d5
3 changed files with 371 additions and 1 deletions

View File

@ -413,6 +413,11 @@
android:authorities="com.getpebble.android.provider"
android:exported="true" />
<provider
android:name=".contentprovider.HRContentProvider"
android:authorities="com.gadgetbridge.heartrate.provider"
android:exported="true" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.screenshot_provider"

View File

@ -0,0 +1,212 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
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
* <p>
* 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<GBDevice> 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();
}
}

View File

@ -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<MiBandActivitySample> 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<Intent> l = shadowOf(RuntimeEnvironment.application).getBroadcastIntents();
assertEquals(10, l.size());
for (Intent i : l)
assertEquals(i.getAction(), DeviceService.ACTION_REALTIME_SAMPLES);
List<BroadcastReceiver> r = shadowOf(RuntimeEnvironment.application).getReceiversForIntent(l.get(0));
assertEquals(1, r.size());
assertEquals(a1.numObserved, 10);
}
}