Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

[WIP][RFC] Introduce a ContentProvider for HR Data #1138

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@

<provider
android:name=".contentprovider.HRContentProvider"
android:authorities="com.gadgetbridge.heartrate.provider"
android:authorities="org.gadgetbridge.realtimesamples.provider"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, but I still don't get the rationale: why org.gadgetbridge instead of nodomain.freeyourgadget.gadgetbridge? I'm not totally opposed, but I'd like to understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I misunderstood you. I'll finally change it to nodomain.freeyourgadget.gadgetbridge

android:exported="true" />

<provider
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer
/* Copyright (C) 2018 Benedikt Elser

This file is part of Gadgetbridge.

Expand Down Expand Up @@ -31,6 +31,9 @@
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

import nodomain.freeyourgadget.gadgetbridge.GBApplication;
Expand All @@ -42,21 +45,16 @@
/**
* A content Provider, which publishes read only RAW @see ActivitySample to other applications
* <p>
* TODO:
* - Contract Class
* - Permission System to read HR Data
* - Fix Travis
* - Check if the Device is really connected - connect and disconnect
* (Is the Selected device the current connected device??)
*/
public class HRContentProvider extends ContentProvider {
private static final Logger LOG = LoggerFactory.getLogger(HRContentProvider.class);

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;

enum provider_state {ACTIVE, INACTIVE};
enum provider_state {ACTIVE, CONNECTING, INACTIVE};
provider_state state = provider_state.INACTIVE;

private static final UriMatcher URI_MATCHER;
Expand All @@ -82,17 +80,20 @@ enum provider_state {ACTIVE, INACTIVE};
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Log.i(HRContentProvider.class.getName(), "Received Event, aciton: " + action);
//LOG.info(HRContentProvider.class.getName(), "Received Event, aciton: " + action);

switch (action) {
case GBDevice.ACTION_DEVICE_CHANGED:
mGBDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);

LOG.debug(HRContentProvider.class.toString(), "Got Device " + mGBDevice);
// Rationale: If device was not connected
// it should show up here after beeing connected
// If the user wanted to switch on realtime traffic, but we first needed to connect it
// we do it here
if (state == provider_state.ACTIVE) {
if (mGBDevice.isConnected() && state == provider_state.CONNECTING) {
LOG.debug(HRContentProvider.class.toString(), "Device connected now, enabling realtime " + mGBDevice);

state = provider_state.ACTIVE;
GBApplication.deviceService().onEnableRealtimeSteps(true);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
}
Expand All @@ -117,7 +118,7 @@ public void onReceive(Context context, Intent intent) {

@Override
public boolean onCreate() {
Log.i(HRContentProvider.class.getName(), "Creating...");
LOG.info(HRContentProvider.class.getName(), "Creating...");
IntentFilter filterLocal = new IntentFilter();

filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
Expand All @@ -136,7 +137,7 @@ public boolean onCreate() {

@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
//Log.i(HRContentProvider.class.getName(), "query uri " + uri.toString());
//LOG.info(HRContentProvider.class.getName(), "query uri " + uri.toString());
MatrixCursor mc;

DeviceManager deviceManager;
Expand All @@ -147,18 +148,20 @@ public Cursor query(@NonNull Uri uri, String[] projection, String selection, Str
if (l == null) {
return null;
}
Log.i(HRContentProvider.class.getName(), String.format("listing %d devices", l.size()));
LOG.info(HRContentProvider.class.getName(), String.format("listing %d devices", l.size()));

mc = new MatrixCursor(HRContentProviderContract.deviceColumnNames);
for (GBDevice dev : l) {
mc.addRow(new Object[]{dev.getName(), dev.getModel(), dev.getAddress()});
}
return mc;
case ACTIVITY_START:
this.state = provider_state.ACTIVE;

this.state = provider_state.CONNECTING;
LOG.info(HRContentProvider.class.getName(), "Get ACTIVTY START");
GBDevice targetDevice = getDevice((selectionArgs != null) ? selectionArgs[0] : "");
if (targetDevice != null && targetDevice.isConnected()) {
Log.i(HRContentProvider.class.getName(), "Get ACTIVTY START");
this.state = provider_state.ACTIVE;
GBApplication.deviceService().onEnableRealtimeSteps(true);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
mc = new MatrixCursor(HRContentProviderContract.activityColumnNames);
Expand All @@ -173,15 +176,15 @@ public Cursor query(@NonNull Uri uri, String[] projection, String selection, Str
case ACTIVITY_STOP:
this.state = provider_state.INACTIVE;

Log.i(HRContentProvider.class.getName(), "Get ACTIVITY STOP");
LOG.info(HRContentProvider.class.getName(), "Get ACTIVITY STOP");
GBApplication.deviceService().onEnableRealtimeSteps(false);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false);
mc = new MatrixCursor(HRContentProviderContract.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));
//LOG.error(HRContentProvider.class.getName(), String.format("Get REALTIME buffered sample %s", sample_string));
mc = new MatrixCursor(HRContentProviderContract.realtimeColumnNames);
if (buffered_sample != null)
mc.addRow(new Object[]{"OK", buffered_sample.getHeartRate(), buffered_sample.getSteps(), mGBDevice != null ? mGBDevice.getBatteryLevel() : 99});
Expand All @@ -195,20 +198,27 @@ public Cursor query(@NonNull Uri uri, String[] projection, String selection, Str
@Nullable
private GBDevice getDevice(String deviceAddress) {
DeviceManager deviceManager;

if (mGBDevice != null && mGBDevice.getAddress() == deviceAddress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be equals(), not ==

LOG.info(HRContentProvider.class.getName(), String.format("Found device mGBDevice %s", mGBDevice));

return mGBDevice;
}

deviceManager = ((GBApplication) (this.getContext())).getDeviceManager();
for (GBDevice device : deviceManager.getDevices()) {
if (deviceAddress.equals(device.getAddress())) {
Log.i(HRContentProvider.class.getName(), String.format("Found device device %s", device));
LOG.info(HRContentProvider.class.getName(), String.format("Found device device %s", device));
return device;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the slf4j logger instead of Log.i, Log, e, etc.

}
}
Log.i(HRContentProvider.class.getName(), String.format("Did not find device returning selected %s", deviceManager.getSelectedDevice()));
LOG.info(HRContentProvider.class.getName(), String.format("Did not find device returning selected %s", deviceManager.getSelectedDevice()));
return deviceManager.getSelectedDevice();
}

@Override
public String getType(@NonNull Uri uri) {
Log.e(HRContentProvider.class.getName(), "getType uri " + uri);
LOG.error(HRContentProvider.class.getName(), "getType uri " + uri);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
/* Copyright (C) 2018 Benedikt Elser

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.net.Uri;

public final class HRContentProviderContract {
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", "Heartrate", "Steps", "Battery"};

static final String AUTHORITY = "com.gadgetbridge.heartrate.provider";
public static final String COLUMN_STATUS = "Status";
public static final String COLUMN_NAME = "Name";
public static final String COLUMN_ADDRESS = "Address";
public static final String COLUMN_MODEL = "Model";
public static final String COLUMN_MESSAGE = "Message";
public static final String COLUMN_HEARTRATE = "HeartRate";
public static final String COLUMN_STEPS = "Steps";
public static final String COLUMN_BATTERY = "Battery";

public static final String[] deviceColumnNames = new String[]{COLUMN_NAME, COLUMN_MODEL, COLUMN_ADDRESS};
public static final String[] activityColumnNames = new String[]{COLUMN_STATUS, COLUMN_MESSAGE};
public static final String[] realtimeColumnNames = new String[]{COLUMN_STATUS, COLUMN_HEARTRATE, COLUMN_STEPS, COLUMN_BATTERY};

static final String AUTHORITY = "org.gadgetbridge.realtimesamples.provider";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

com.gadgetbridge again..

static final String ACTIVITY_START_URL = "content://" + AUTHORITY + "/activity_start";
static final String ACTIVITY_STOP_URL = "content://" + AUTHORITY + "/activity_stop";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;

import org.junit.Test;

Expand Down Expand Up @@ -36,9 +35,12 @@

import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowContentResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class SampleProviderTest extends TestBase {
private static final Logger LOG = LoggerFactory.getLogger(SampleProviderTest.class);

private GBDevice dummyGBDevice;
private ContentResolver mContentResolver;
Expand All @@ -53,7 +55,7 @@ public void setUp() throws Exception {
// Stuff context into provider
provider.attachInfo(app.getApplicationContext(), null);

ShadowContentResolver.registerProviderInternal("com.gadgetbridge.heartrate.provider", provider);
ShadowContentResolver.registerProviderInternal("org.gadgetbridge.realtimesamples.provider", provider);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

com.gadgetbr...


@Test
Expand Down Expand Up @@ -204,7 +206,7 @@ private void generateSampleStream(MiBandSampleProvider sampleProvider) {

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());
//LOG.debug(SampleProviderTest.class.getName(), "Sending sample " + sample.toString());
Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
.putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
app.sendBroadcast(intent);
Expand Down Expand Up @@ -286,15 +288,15 @@ class A1 extends ContentObserver {
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
//Log.e(SampleProviderTest.class.getName(), "Changed " + uri.toString());
//LOG.error(SampleProviderTest.class.getName(), "Changed " + uri.toString());
Cursor cursor = mContentResolver.query(HRContentProviderContract.REALTIME_URI, null, null, null, null);
if (cursor.moveToFirst()) {
do {
String status = cursor.getString(0);
int heartRate = cursor.getInt(1);
assertEquals("OK", status);
assertEquals(60 + 5*numObserved, heartRate);
Log.i("test", "HeartRate " + heartRate);
LOG.info("test", "HeartRate " + heartRate);
} while (cursor.moveToNext());
}
numObserved++;
Expand Down