diff --git a/android_app/app/src/main/AndroidManifest.xml b/android_app/app/src/main/AndroidManifest.xml
index 30e2c975..d7cecce9 100644
--- a/android_app/app/src/main/AndroidManifest.xml
+++ b/android_app/app/src/main/AndroidManifest.xml
@@ -9,6 +9,15 @@
+
+
+
+
+
diff --git a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java
index 247c319e..53a5f2cb 100644
--- a/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java
+++ b/android_app/app/src/main/java/com/health/openscale/core/OpenScale.java
@@ -606,4 +606,19 @@ public class OpenScale {
}
}
}
+
+ // As getScaleUserList(), but as a Cursor for export via a Content Provider.
+ public Cursor getScaleUserListCursor() {
+ return userDAO.selectAll();
+ }
+
+ // As getScaleUser(), but as a Cursor for export via a Content Provider.
+ public Cursor getScaleUserCursor(int userId) {
+ return userDAO.select(userId);
+ }
+
+ // As getScaleMeasurementList(), but as a Cursor for export via a Content Provider.
+ public Cursor getScaleMeasurementListCursor(int userId) {
+ return measurementDAO.selectAll(userId);
+ }
}
diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleMeasurementDAO.java b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleMeasurementDAO.java
index dba85587..085c93e9 100644
--- a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleMeasurementDAO.java
+++ b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleMeasurementDAO.java
@@ -21,6 +21,7 @@ import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
+import android.database.Cursor;
import com.health.openscale.core.datatypes.ScaleMeasurement;
@@ -64,4 +65,8 @@ public interface ScaleMeasurementDAO {
@Query("DELETE FROM scaleMeasurements WHERE userId = :userId")
void deleteAll(int userId);
+
+ // selectAll() is equivalent to getAll(), but returns a Cursor, for exposing via a ContentProvider.
+ @Query("SELECT * FROM scaleMeasurements WHERE userId = :userId AND enabled = 1 ORDER BY datetime DESC")
+ Cursor selectAll(int userId);
}
diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDAO.java b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDAO.java
index 71feb128..589ce496 100644
--- a/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDAO.java
+++ b/android_app/app/src/main/java/com/health/openscale/core/database/ScaleUserDAO.java
@@ -21,6 +21,7 @@ import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
+import android.database.Cursor;
import com.health.openscale.core.datatypes.ScaleUser;
@@ -45,4 +46,13 @@ public interface ScaleUserDAO {
@Delete
void delete(ScaleUser user);
+
+ // selectAll() and select() are equivalent to getall() and get(), but return a Cursor,
+ // for exposing via a ContentProvider.
+ @Query("SELECT * FROM scaleUsers")
+ Cursor selectAll();
+
+ @Query("SELECT * FROM scaleUsers WHERE id = :id")
+ Cursor select(int id);
+
}
diff --git a/android_app/app/src/main/java/com/health/openscale/core/export/OpenScaleContentProvider.java b/android_app/app/src/main/java/com/health/openscale/core/export/OpenScaleContentProvider.java
new file mode 100644
index 00000000..0c5c843b
--- /dev/null
+++ b/android_app/app/src/main/java/com/health/openscale/core/export/OpenScaleContentProvider.java
@@ -0,0 +1,129 @@
+/* Copyright (C) 2018 Paul Cowan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ */package com.health.openscale.core.export;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.health.openscale.core.OpenScale;
+
+/**
+ * Exposes the user and measurement data from openScale via
+ * Android
+ * Content Providers. This allows other apps to access the openScale data for their own purposes
+ * (e.g. syncing to third-party services like Google Fit, Fitbit API, etc) without openScale itself
+ * needing to do so or request additional permissions.
+ *
+ * This access is gated by the com.health.openscale.READ_DATA permission, which is defined in the
+ * manifest; it is not accessible to any other app without user confirmation.
+ *
+ * The following URIs are supported:
+ *
+ * content://com.health.openscale.provider/user
: list all users.
+ * content://com.health.openscale.provider/user/$ID
: retrieve single user
+ * by ID.
+ * content://com.health.openscale.provider/user/$ID/measurements
:
+ * retrieve all measurements for the supplied user ID.
+ *
+ */
+public class OpenScaleContentProvider extends ContentProvider {
+ private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final String AUTHORITY = "com.health.openscale.provider";
+
+ private static final int MATCH_TYPE_USER_LIST = 1;
+ private static final int MATCH_TYPE_USER_ENTRY = 2;
+ private static final int MATCH_TYPE_USER_MEASUREMENTS = 3;
+
+
+ static {
+ uriMatcher.addURI(AUTHORITY, "user", MATCH_TYPE_USER_LIST);
+ uriMatcher.addURI(AUTHORITY, "user/#", MATCH_TYPE_USER_ENTRY);
+ uriMatcher.addURI(AUTHORITY, "user/#/measurements", MATCH_TYPE_USER_MEASUREMENTS);
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (uriMatcher.match(uri)) {
+ case MATCH_TYPE_USER_LIST:
+ return "vnd.android.cursor.dir/vnd.com.health.openscale.provider.user";
+
+ case MATCH_TYPE_USER_ENTRY:
+ return "vnd.android.cursor.item/vnd.com.health.openscale.provider.user";
+
+ case MATCH_TYPE_USER_MEASUREMENTS:
+ return "vnd.android.cursor.item/vnd.com.health.openscale.provider.measurement";
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ final Context context = getContext();
+ Cursor cursor;
+
+ switch (uriMatcher.match(uri)) {
+ case MATCH_TYPE_USER_LIST:
+ cursor = OpenScale.getInstance().getScaleUserListCursor();
+ break;
+
+ case MATCH_TYPE_USER_ENTRY:
+ cursor = OpenScale.getInstance().getScaleUserCursor(Integer.valueOf(uri.getPathSegments().get(1)));
+ break;
+
+ case MATCH_TYPE_USER_MEASUREMENTS:
+ cursor = OpenScale.getInstance().getScaleMeasurementListCursor(Integer.valueOf(uri.getPathSegments().get(1)));
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+
+ cursor.setNotificationUri(context.getContentResolver(), uri);
+ return cursor;
+ }
+
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+}
diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml
index a10bee75..f850aee0 100644
--- a/android_app/app/src/main/res/values/strings.xml
+++ b/android_app/app/src/main/res/values/strings.xml
@@ -190,6 +190,8 @@
Permission not granted
Coarse location permission needed to search for Bluetooth devices. It can be revoked after the device is found.
Info
+ read openScale data, including user information and all saved measurements
+ Read openScale Data
Next
Long press and drag measurement to reorder
Click measurement to configure