Я и мои товарищи по команде используем MI band 3 для записи данных из Google Fit API. Мы используем стороннее приложение - «Уведомление и фитнес для MI Band» для синхронизации данных. Мы не можем получить мгновенную частоту пульса.
Мы используем DataType.TYPE_HEART_RATE_BPM
, DataType.AGGREGATE_HEART_RATE_SUMMARY
, чтобы получить средние самые высокие и самые низкие показания сердца, но мгновенные показания не отображаются.
package com.example.shubchintak;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.fitness.Fitness;
import com.google.android.gms.fitness.FitnessOptions;
import com.google.android.gms.fitness.data.Bucket;
import com.google.android.gms.fitness.data.DataPoint;
import com.google.android.gms.fitness.data.DataSet;
import com.google.android.gms.fitness.data.DataSource;
import com.google.android.gms.fitness.data.DataType;
import com.google.android.gms.fitness.data.Field;
import com.google.android.gms.fitness.data.Subscription;
import com.google.android.gms.fitness.data.Value;
import com.google.android.gms.fitness.request.DataReadRequest;
import com.google.android.gms.fitness.request.DataSourcesRequest;
import com.google.android.gms.fitness.request.OnDataPointListener;
import com.google.android.gms.fitness.request.SensorRequest;
import com.google.android.gms.fitness.result.DataReadResponse;
import com.google.android.gms.fitness.result.DataReadResult;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.logging.LogWrapper;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.text.DateFormat.getDateInstance;
import static java.text.DateFormat.getTimeInstance;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private Toolbar mToolbar;
private DrawerLayout mdrawerLayoout;
public static final String TAG = "StepCounter";
private static final int REQUEST_OAUTH_REQUEST_CODE = 0x1001;
private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 0x1001;
static TextView stepsNo;
private FirebaseAuth mAuth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.main_page_toolbar);
setSupportActionBar(mToolbar);
getSupportActionBar().setTitle("Shubchintak");
stepsNo=(TextView)findViewById(R.id.main_stat);
mdrawerLayoout =(DrawerLayout)findViewById(R.id.main_activity);
NavigationView navigationView=findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
FitnessOptions fitnessOptions =
FitnessOptions.builder()
.addDataType(DataType.TYPE_HEART_RATE_BPM)
.addDataType(DataType.AGGREGATE_HEART_RATE_SUMMARY)
.build();
if (!GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(this), fitnessOptions)) {
GoogleSignIn.requestPermissions(
this,
REQUEST_OAUTH_REQUEST_CODE,
GoogleSignIn.getLastSignedInAccount(this),
fitnessOptions);
} else {
subscribe();
}
if (!checkPermissions()) {
requestPermissions();
} else {
}
mHandler = new Handler();
startRepeatingTask();
ActionBarDrawerToggle toggle=new ActionBarDrawerToggle(this,mdrawerLayoout,mToolbar,
R.string.navigation_drawer_open,R.string.navigation_drawer_close);
mdrawerLayoout.addDrawerListener(toggle);
toggle.syncState();
}
private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION);
int permissionState1 = ActivityCompat.checkSelfPermission(this,
Manifest.permission.BODY_SENSORS);
return permissionState == PackageManager.PERMISSION_GRANTED && permissionState1 == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.BODY_SENSORS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.BODY_SENSORS)) {
// Show an explanation to the user asynchronously -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.BODY_SENSORS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_OAUTH_REQUEST_CODE) {
subscribe();
}
}
}
public void subscribe() {
// To create a subscription, invoke the Recording API. As soon as the subscription is
// active, fitness data will start recording.
Fitness.getRecordingClient(this, GoogleSignIn.getLastSignedInAccount(this))
.subscribe(DataType.AGGREGATE_HEART_RATE_SUMMARY)
.addOnCompleteListener(
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.i(TAG, "Successfully subscribed!");
} else {
Log.w(TAG, "There was a problem subscribing.", task.getException());
}
}
});
}
public static DataReadRequest queryFitnessData() {
// [START build_read_data_request]
// Setting a start and end date using a range of 1 week before this moment.
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
java.text.DateFormat dateFormat = getDateInstance();
Log.i(TAG, "Range Start: " + dateFormat.format(startTime));
Log.i(TAG, "Range End: " + dateFormat.format(endTime));
DataReadRequest readRequest =
new DataReadRequest.Builder()
// The data request can specify multiple data types to return, effectively
// combining multiple data queries into one call.
// In this example, it's very unlikely that the request is for several hundred
// datapoints each consisting of a few steps and a timestamp. The more likely
// scenario is wanting to see how many steps were walked per day, for 7 days.
.aggregate(DataType.TYPE_HEART_RATE_BPM, DataType.AGGREGATE_HEART_RATE_SUMMARY) // Analogous to a "Group By" in SQL, defines how data should be aggregated.
// bucketByTime allows for a time span, whereas bucketBySession would allow
// bucketing by "sessions", which would need to be defined in code.
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build();
// [END build_read_data_request]
return readRequest;
}
private void readData() {
DataReadRequest readRequest = queryFitnessData();
Fitness.getHistoryClient(this, GoogleSignIn.getLastSignedInAccount(this))
.readData(readRequest)
.addOnSuccessListener(
new OnSuccessListener<DataReadResponse>() {
@Override
public void onSuccess(DataReadResponse dataReadResponse) {
printData(dataReadResponse);
}
})
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.w(TAG, "There was a problem getting the step count.", e);
}
});
}
public static void printData(DataReadResponse dataReadResult) {
// [START parse_read_data_result]
// If the DataReadRequest object specified aggregated data, dataReadResult will be returned
// as buckets containing DataSets, instead of just DataSets.
if (dataReadResult.getBuckets().size() > 0) {
Log.i(
TAG, "Number of returned buckets of DataSets is: " + dataReadResult.getBuckets().size());
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
dumpDataSet(dataSet);
}
}
} else if (dataReadResult.getDataSets().size() > 0) {
Log.i(
TAG, "Number of returned buckets of DataSets is:ajsbdajsb " );
}
// [END parse_read_data_result]
}
private static void dumpDataSet(DataSet dataSet) {
Log.i(TAG, "Data returned for Data type: " + dataSet.getDataType().getName());
DateFormat dateFormat = getTimeInstance();
for (DataPoint dp : dataSet.getDataPoints()) {
Log.i(TAG, "Data point:");
Log.i(TAG, "\tType: " + dp.getDataType().getName());
Log.i(TAG, "\tStart: " + dateFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)));
Log.i(TAG, "\tEnd: " + dateFormat.format(dp.getEndTime(TimeUnit.MILLISECONDS)));
for (Field field : dp.getDataType().getFields()) {
Log.i(TAG, "\tField: " + field.getName() + " Value: " + dp.getValue(field));
if(field.getName().equals("average")){
stepsNo.setText(dp.getValue(field).toString());
}
}
}
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
switch(menuItem.getItemId()) {
case R.id.nav_logout:
FirebaseAuth.getInstance().signOut();
sendToStart();
break;
case R.id.action_read_data:
Log.i(TAG, "Entered method read data");
readData();
break;
}
mdrawerLayoout.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onBackPressed() {
if(mdrawerLayoout.isDrawerOpen(GravityCompat.START)){
mdrawerLayoout.closeDrawer(GravityCompat.START);
}else {
super.onBackPressed();
}
}
@Override
public void onStart() {
super.onStart();
// Check if user is signed in (non-null) and update UI accordingly.
FirebaseUser currentUser = mAuth.getCurrentUser();
if(currentUser == null){
// sendToStart();
}
}
private void sendToStart() {
Intent startIntent = new Intent(MainActivity.this, StartActivity.class);
startActivity(startIntent);
finish();
}
private int mInterval = 3000; // 5 seconds by default, can be changed later
private Handler mHandler;
@Override
public void onDestroy() {
super.onDestroy();
stopRepeatingTask();
}
Runnable mStatusChecker = new Runnable() {
@Override
public void run() {
try {
readData(); //this function can change value of mInterval.
} finally {
// 100% guarantee that this always happens, even if
// your update method throws an exception
mHandler.postDelayed(mStatusChecker, mInterval);
}
}
};
void startRepeatingTask() {
mStatusChecker.run();
}
void stopRepeatingTask() {
mHandler.removeCallbacks(mStatusChecker);
}
}
Ожидаемое решение: отображать мгновенные данные