mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-25 16:15:55 +01:00
[WIP] Introduce a ContentProvider for HR Data
This commit is contained in:
parent
86038a7abf
commit
1d1cd006d5
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user