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