Android Question Samsung S-Health

Rusty

Well-Known Member
Licensed User
Longtime User
I am trying to use the Samsung S-Health application to monitor activity etc.
I've got the .jar file samsung-digital-health-healthdata-1.2.1.jar in my additional libraries folder.
I also have ported the Java code:
B4X:
#if java
import com.samsung.android.sdk.healthdata.HealthConstants;
import com.samsung.android.sdk.healthdata.HealthDataObserver;
import com.samsung.android.sdk.healthdata.HealthDataResolver;
import com.samsung.android.sdk.healthdata.HealthDataResolver.Filter;
import com.samsung.android.sdk.healthdata.HealthDataResolver.ReadRequest;
import com.samsung.android.sdk.healthdata.HealthDataResolver.ReadResult;
import com.samsung.android.sdk.healthdata.HealthDataStore;
import com.samsung.android.sdk.healthdata.HealthResultHolder;

import android.database.Cursor;
import android.util.Log;

import java.util.Calendar;

public class StepCountReporter {
    private final HealthDataStore mStore;

    public StepCountReporter(HealthDataStore store) {
        mStore = store;
    }

    public void start() {
        // Register an observer to listen changes of step count and get today step count
        HealthDataObserver.addObserver(mStore, HealthConstants.StepCount.HEALTH_DATA_TYPE, mObserver);
        readTodayStepCount();
    }

    // Read the today's step count on demand
    private void readTodayStepCount() {
        HealthDataResolver resolver = new HealthDataResolver(mStore, null);

        // Set time range from start time of today to the current time
        long startTime = getStartTimeOfToday();
        long endTime = System.currentTimeMillis();
        Filter filter = Filter.and(Filter.greaterThanEquals(HealthConstants.StepCount.START_TIME, startTime),
                                   Filter.lessThanEquals(HealthConstants.StepCount.START_TIME, endTime));

        HealthDataResolver.ReadRequest request = new ReadRequest.Builder()
                                                        .setDataType(HealthConstants.StepCount.HEALTH_DATA_TYPE)
                                                        .setProperties(new String[] {HealthConstants.StepCount.COUNT})
                                                        .setFilter(filter)
                                                        .build();

        try {
            resolver.read(request).setResultListener(mListener);
        } catch (Exception e) {
            Log.e(MainActivity.APP_TAG, e.getClass().getName() + " - " + e.getMessage());
            Log.e(MainActivity.APP_TAG, "Getting step count fails.");
        }
    }

    private long getStartTimeOfToday() {
        Calendar today = Calendar.getInstance();

        today.set(Calendar.HOUR_OF_DAY, 0);
        today.set(Calendar.MINUTE, 0);
        today.set(Calendar.SECOND, 0);
        today.set(Calendar.MILLISECOND, 0);

        return today.getTimeInMillis();
    }

    private final HealthResultHolder.ResultListener<ReadResult> mListener = new HealthResultHolder.ResultListener<ReadResult>() {
        @Override
        public void onResult(ReadResult result) {
            int count = 0;
            Cursor c = null;

            try {
                c = result.getResultCursor();
                if (c != null) {
                    while (c.moveToNext()) {
                        count += c.getInt(c.getColumnIndex(HealthConstants.StepCount.COUNT));
                    }
                }
            } finally {
                if (c != null) {
                    c.close();
                }
            }
            MainActivity.getInstance().drawStepCount(String.valueOf(count));
        }
    };

    private final HealthDataObserver mObserver = new HealthDataObserver(null) {

        // Update the step count when a change event is received
        @Override
        public void onChange(String dataTypeName) {
            Log.d(MainActivity.APP_TAG, "Observer receives a data changed event");
            readTodayStepCount();
        }
    };

}
    #end if
into my new application.
However, when I refresh my libraries, the Samsung jar doesn't show.
Any ideas why it doesn't show? (Also, any advice on making the Java code above work would be GREATLY appreciated!)
Rusty
 

DonManfred

Expert
Licensed User
Longtime User
Update:
Inside the wrapper the resultcursor IS a SQL Cursor-Type but it is not compatible with b4a Cursortype it seems.
So i try now to create a List of Maps for the cursor-contents and then return this list in the event...
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
Thanks...I'm not clear how I'd make a list of Maps from this cursor...
Can you give me an idea?
Rusty
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Can you give me an idea?
B4X:
          Cursor c = null;
          try {
              c = result.getResultCursor();
              if (c == null) {
                  BA.Log("null cursor!");
                  return;
              }
              BA.Log("CursorSize="+c.getCount());
              List l = new anywheresoftware.b4a.objects.collections.List();
              l.Initialize();

              // Read the food intake calories of each meal type
              while (c.moveToNext()) {
                  Map m = new anywheresoftware.b4a.objects.collections.Map();
                  m.Initialize();
                  m.Put("CALORIE", c.getInt(c.getColumnIndex(StepCount.CALORIE)));
                  m.Put("COUNT", c.getInt(c.getColumnIndex(StepCount.COUNT)));
                  m.Put("DISTANCE", c.getString(c.getColumnIndex(StepCount.DISTANCE)));
                  m.Put("SPEED", c.getString(c.getColumnIndex(StepCount.SPEED)));
                  m.Put("HEALTH_DATA_TYPE", c.getString(c.getColumnIndex(StepCount.HEALTH_DATA_TYPE)));
                  m.Put("SAMPLE_POSITION_TYPE", c.getString(c.getColumnIndex(StepCount.SAMPLE_POSITION_TYPE)));
                  m.Put("CREATE_TIME", c.getLong(c.getColumnIndex(StepCount.CREATE_TIME)));
                  m.Put("DEVICE_UUID", c.getString(c.getColumnIndex(StepCount.DEVICE_UUID)));
                  m.Put("END_TIME", c.getLong(c.getColumnIndex(StepCount.END_TIME)));
                  m.Put("PACKAGE_NAME", c.getString(c.getColumnIndex(StepCount.PACKAGE_NAME)));
                  m.Put("START_TIME", c.getLong(c.getColumnIndex(StepCount.START_TIME)));
                  m.Put("TIME_OFFSET", c.getLong(c.getColumnIndex(StepCount.TIME_OFFSET)));
                  m.Put("UPDATE_TIME", c.getLong(c.getColumnIndex(StepCount.UPDATE_TIME)));
                  m.Put("UUID", c.getString(c.getColumnIndex(StepCount.UUID)));
                  l.Add(m);
              }
              //app.raiseEvent(app.context, eventName+"_pagerendered", i, pageCount, filename+"-" + i + ".png");
              ba.raiseEventFromDifferentThread(this, null, 0, eventName + "_onreadresult", true, new Object[] {result.getCount(),result.getDataType(),result.getStatus(),l});
          } finally {
              if (c != null) {
                  c.close();
              }
          }

But i´m struggling to get data at all... The Cursor is - as of now - always empty... I guess i need to add the right values to the ReadRequestBuilder. For now i´m unsuccessful with my tryings
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
I'm getting:
B4A version: 6.30
Parsing code. (0.01s)
Compiling code. (0.07s)
Compiling layouts code. (0.00s)
Organizing libraries. (0.00s)
Generating R file. (0.21s)
Compiling debugger engine code. (1.87s)
Compiling generated Java code. Error
B4A line: 103
End Sub
javac 1.8.0_51
src\b4a\example\main.java:592: error: cannot find symbol
Cursor c = null;
^
symbol: class Cursor
location: class main

I included your code in an #if java... then ran it and got the above error...ideas?
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
now I'm getting:
B4A version: 6.30
Parsing code. (0.01s)
Compiling code. (0.05s)
Compiling layouts code. (0.00s)
Organizing libraries. (0.00s)
Generating R file. (0.13s)
Compiling debugger engine code. (1.52s)
Compiling generated Java code. Error
B4A line: 103
End Sub
javac 1.8.0_51
src\b4a\example\main.java:594: error: cannot find symbol
c = result.getResultCursor();
^
symbol: variable result
location: class main

EDIT: I don't see where result is dimmed or defined anywhere in the code...
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Hi all,

I started doing it my way, and got a working example. No wrapper, just packed everything with inline Java and tied the receive event to it, so perhaps it is less portable

There are no logs not protections in case it does not connect to the service, but this part can be improved

(Thanks Don for the developer mode trick, otherwise I would be totally stuck at it. :))
 

Attachments

  • sHealthExample.zip
    9.1 KB · Views: 285
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
Nice Jordi!
Are you able to get other items like start time, stop time, distance, calories...etc.?
 
Upvote 0

JordiCP

Expert
Licensed User
Longtime User
Sure, here it is

I experienced a strange behaviour after adding calorie and distance --> I could receive a first set of values, but then there were no more updates. Even after rebooting the phone. I changed the package name and now I receive updates. Perhaps it was only in my phone, my permissions management (quite dirty) or the Samsung app itself. The fact is that with a new package name everything works ok.

If you compare this code with the previous one you will see what has changed so you will be able to add more data.

But I would recommend organizing it in maps as in DonMafred's approach, since it is more general, clean and easier to send to the B4A code
 

Attachments

  • sHealthExample2.zip
    9.3 KB · Views: 273
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
Hi Don,
Did you ever get back to your solution?
If so, what might the results look like?
Thanks,
Rusty
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Did you ever get back to your solution?
Not really. After i saw @JordiCP version running i stopped my dev as as of now i did not get any results...
I guess it is because in my wrap the Filter is not really used... I need to extend my lib to support the "Filter" too i guess
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
With DonManfred and JordiCP's help, I've got the Android device reading the S-Health data. :)
However, the DATE_START, and other dates have a very large LONG value that is not readily parsable with the B4a DateTime functions.

S-Health Start_Time: 17741427420000
Android DateTimeNow: 1478463308436

I am wondering if the Start_Time is based off of 1900-01-01 or some other date like that, since the Millis is so large.
If so, can I just subtract a constant from the S-Health number to relate it to a B4a DateTime function result?

UPDATE: Suffering from brain cramps, I guess.
My cursor read was accumulating the long value of ticks instead of reading it individually each time... All is ok, the timers are naturally in sync...thanks anyway :)

Regards,
Rusty
 
Last edited:
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
Don and Jordi,
I appreciate your help on this.
Can you advise me how either of your solutions can/might be included within a service or a widget?
Thanks,
Rusty
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
Does anyone know how to convert this to a service or widget that can run in the background?
Thanks
Rusty
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
B4X:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim hds As HealthDataStore
    Dim resolv As HealthDataResolver
    Dim pms As HealthPermissionManager
    Dim dhs As HealthDataService
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
    dhs.Initialize("Service")   
    hds.Initialize("Health")
    hds.ConnectService
    'Log(dhs.getisFeatureEnabled(0))
    'Log(hds.MyUserId)
    'Log(hds.PlatformPackageName)   
    resolv.Initialize("Resolver",hds)
End Sub
Sub Activity_Resume
End Sub
Sub Activity_Pause (UserClosed As Boolean)
End Sub
Sub Health_onConnected()   
    Log($"Health_onConnected()"$)
    pms.Initialize("HealthPermission",hds)
    pms.checkandrequestPermissionAcquired(pms.StepCountPermission)

    If pms.getPermissionGranted(pms.StepCountPermission) Then
        Log("Request read")
        Dim f As Filter
       
        Dim startdate As Long
        startdate = DateTime.Add(DateTime.Now,0,0,-1)
        Dim enddate As Long
        enddate = DateTime.Now
       
        f.initialize(startdate,enddate)
        'f.greaterThan("",startdate)
        'f.lessThan("",enddate)
        '.lessThan("",DateTime.Now)
       
        Dim readreq As ReadRequestBuilder
        readreq.Initialize.setResultCount(0,1000000).setDataType("com.samsung.health.step_count").setFilter(f)
        resolv.read(readreq.build)
    Else
    End If
   
   
End Sub
Sub Health_onConnectionFailed(errorCode As Int, hasResolution As Boolean,error As String)
    Log($"Health_onConnectionFailed(${error},${errorCode},${hasResolution})"$)
End Sub
Sub Health_onDisConnected()   
    Log($"Health_onDisConnected()"$)
End Sub
Sub HealthPermission_onPermissionResult(Count As Int, granted As Boolean,status As Int)
    Log($"HealthPermission_onPermissionResult(${Count},${status},${granted})"$)
End Sub
Sub Resolver_onAggregateResult(count As Int, valuetype As String, status As Int, data As Cursor)
    Log($"Resolver_onAggregateResult(${count},${status})"$)
    If data <> Null And data.IsInitialized Then
        Log($"Resolver_onAggregateResult(cursor != null)"$)       
    End If

End Sub
Sub Resolver_onDeleteResult(count As Int, status As Int)
    Log($"Resolver_onDeleteResult(${count},${status})"$)   
End Sub
Sub Resolver_onInsertResult(count As Int, status As Int)
    Log($"Resolver_onInsertResult(${count},${status})"$)   
End Sub
Sub Resolver_onReadResult(count As Int, valuetype As String, status As Int, data As List)
    Log($"Resolver_onReadResult(${count},${status})"$)
    If data <> Null And data.IsInitialized Then
        Log($"Resolver_onReadResult(cursor != null)"$)       
        Dim count As Int
        If data.Size > 0 Then
            For i = 0 To data.Size-1
                Log(data.Get(i))
                Dim m As Map = data.Get(i)
                count = count + m.Get("COUNT")
            Next
        End If
        Log($"StepCount = ${count}"$)
    End If
End Sub
Sub Resolver_onUpdateResult(count As Int, status As Int)
    Log($"Resolver_onUpdateResult(${count},${status})"$)
End Sub

** Service (starter) Create **
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
lib:Raising.. health_onconnected()
Health_onConnected()
Request read
lib:Raising.. resolver_onreadresult()
CursorSize=171
Resolver_onReadResult(171,1)
Resolver_onReadResult(cursor != null)
{CALORIE=0, COUNT=27, DISTANCE=20.97, SPEED=1.33333, SAMPLE_POSITION_TYPE=null, CREATE_TIME=1478794998754, DEVICE_UUID=ul2IO4xb74, END_TIME=1478794980000, PACKAGE_NAME=com.sec.android.app.shealth, START_TIME=1478794920000, TIME_OFFSET=3600000, UPDATE_TIME=1478794998754, UUID=88a004ff-da4b-4d37-a178-ff9abcc44fc2}
[...]
StepCount = 6977
** Activity (main) Pause, UserClosed = false **
** Activity (main) Resume **
 

Attachments

  • SamsungDigitalHealthV1.1.zip
    195.2 KB · Views: 294
  • SHealthEx.zip
    7.6 KB · Views: 281
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
fantastic Don!
it gets the data but dies on:
B4X:
count = count + m.Get("COUNT")
error: java.lang.ClassCastException: anywheresoftware.b4a.objects.collections.Map cannot be cast to anywheresoftware.b4a.objects.collections.Map$MyMap
Your thoughts?
Thanks,
Rusty
 
Upvote 0

Rusty

Well-Known Member
Licensed User
Longtime User
:) That works great.
Any chance you can share the java code.
I've been working in Android Studio in order to figure all this out and I'd really like to see how it's done.

Thanks,
Rusty
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Any chance you can share the java code.
I've been working in Android Studio in order to figure all this out and I'd really like to see how it's done.
I´m using Eclipse and compile it with SLC.

Please find the source attached. Feel free to extend it and reshare it
 

Attachments

  • src.zip
    12.7 KB · Views: 279
Upvote 0
Top