Created
June 8, 2018 20:34
-
-
Save francislainy/c4fa12d1122fde80e932444b7498aa16 to your computer and use it in GitHub Desktop.
GoogleFit Get Distance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import android.Manifest; | |
| import android.app.Activity; | |
| import android.content.pm.PackageManager; | |
| import android.os.AsyncTask; | |
| import android.os.Bundle; | |
| import android.support.annotation.NonNull; | |
| import android.support.annotation.Nullable; | |
| import android.support.v4.app.ActivityCompat; | |
| import android.support.v4.app.FragmentManager; | |
| import android.util.Log; | |
| import android.widget.Toast; | |
| import com.google.android.gms.common.ConnectionResult; | |
| import com.google.android.gms.common.Scopes; | |
| import com.google.android.gms.common.api.GoogleApiClient; | |
| import com.google.android.gms.common.api.PendingResult; | |
| import com.google.android.gms.common.api.ResultCallback; | |
| import com.google.android.gms.common.api.Scope; | |
| import com.google.android.gms.common.api.Status; | |
| import com.google.android.gms.fitness.Fitness; | |
| import com.google.android.gms.fitness.FitnessStatusCodes; | |
| 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.Value; | |
| import com.google.android.gms.fitness.request.DataReadRequest; | |
| import com.google.android.gms.fitness.result.DailyTotalResult; | |
| import com.google.android.gms.fitness.result.DataReadResult; | |
| import com.google.gson.JsonObject; | |
| import java.text.DateFormat; | |
| import java.text.ParseException; | |
| import java.text.SimpleDateFormat; | |
| import java.util.Calendar; | |
| import java.util.Date; | |
| import java.util.GregorianCalendar; | |
| import java.util.List; | |
| import java.util.concurrent.TimeUnit; | |
| import static java.text.DateFormat.getDateInstance; | |
| /** | |
| * Created by fcampos on 17/05/2018. | |
| */ | |
| public class GoogleFitUtils { | |
| private final static String LOG_TAG = GoogleFitUtils.class.getName(); | |
| private static final int ACCESS_FINE_LOCATION_REQUEST_CODE = 200; | |
| private static final int SENSORS_REQUEST_CODE = 300; | |
| private static boolean checkPermissionsAccessFineLocation() { | |
| int permissionState = ActivityCompat.checkSelfPermission(App.getInstance(), | |
| Manifest.permission.ACCESS_FINE_LOCATION); | |
| return permissionState == PackageManager.PERMISSION_GRANTED; | |
| } | |
| public static void checkSensorsPermission(Activity activity) { | |
| if (!checkPermissionsBodySensor()) { | |
| if (activity != null) { | |
| ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.BODY_SENSORS}, SENSORS_REQUEST_CODE); | |
| } | |
| } | |
| } | |
| public static void checkAccessFineLocationPermission(Activity activity) { | |
| if (!checkPermissionsAccessFineLocation()) { | |
| if (activity != null) { | |
| ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, ACCESS_FINE_LOCATION_REQUEST_CODE); | |
| } | |
| } | |
| } | |
| // Sensor | |
| private static boolean checkPermissionsBodySensor() { | |
| int permissionState = ActivityCompat.checkSelfPermission(App.getInstance(), | |
| Manifest.permission.BODY_SENSORS); | |
| return permissionState == PackageManager.PERMISSION_GRANTED; | |
| } | |
| private static void updateDistanceGoogle(float distanceCount, FragmentManager fm) { | |
| Log.d(LOG_TAG, "updateDistanceGoogle ===== " + distanceCount); | |
| HomeFragment homeFrag = (HomeFragment) fm.findFragmentByTag(String.valueOf(MainActivity.HOME_MAIN)); | |
| if (homeFrag != null && homeFrag.isVisible()) { | |
| homeFrag.distanceFromGoogleFit(distanceCount); | |
| } | |
| } | |
| /* | |
| Build a GoogleApiClient that will authenticate the user and allow the application to connect to Fitness APIs. | |
| */ | |
| public static GoogleApiClient buildFitnessClient(GoogleApiClient client, Activity activity) { | |
| Log.d(LOG_TAG, "buildFitnessClient called"); | |
| if (client == null && GoogleFitUtils.checkPermissionsAccessFineLocation() && GoogleFitUtils.checkPermissionsBodySensor()) { | |
| client = new GoogleApiClient.Builder(activity) | |
| .addApi(Fitness.RECORDING_API) | |
| .addApi(Fitness.HISTORY_API) | |
| .addApi(Fitness.SENSORS_API) | |
| .addScope(new Scope(Scopes.FITNESS_LOCATION_READ)) | |
| .addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE)) | |
| .addConnectionCallbacks( | |
| new GoogleApiClient.ConnectionCallbacks() { | |
| @Override | |
| public void onConnected(@Nullable Bundle bundle) { | |
| Log.d(LOG_TAG, "buildFitnessClient connected"); | |
| // Now you can make calls to the Fitness APIs | |
| // the HomeFragment will call the "subscribeDailySteps()" | |
| } | |
| @Override | |
| public void onConnectionSuspended(int i) { | |
| Log.i(LOG_TAG, "buildFitnessClient connection suspended"); | |
| if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) { | |
| Log.w(LOG_TAG, "Connection lost. Cause: Network Lost."); | |
| } else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) { | |
| Log.w(LOG_TAG, "Connection lost. Reason: Service Disconnected"); | |
| } | |
| } | |
| } | |
| ) | |
| .enableAutoManage((MainActivity) activity, 0, new GoogleApiClient.OnConnectionFailedListener() { | |
| @Override | |
| public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { | |
| Log.e(LOG_TAG, "Google Play services failed. Cause: " + connectionResult.toString()); | |
| } | |
| }) | |
| .build(); | |
| } | |
| return client; | |
| } | |
| private static DataReadRequest getDataDistanceReadRequest(int numberOfDaysPast) { | |
| Calendar cal = new GregorianCalendar(); | |
| cal.setTime(new Date()); | |
| cal.add(Calendar.DAY_OF_YEAR, -numberOfDaysPast); | |
| cal.set(Calendar.HOUR_OF_DAY, 0); | |
| cal.set(Calendar.MINUTE, 0); | |
| cal.set(Calendar.SECOND, 0); | |
| cal.set(Calendar.MILLISECOND, 0); | |
| long endTime = cal.getTimeInMillis(); | |
| cal.add(Calendar.DAY_OF_YEAR, -1); | |
| long startTime = cal.getTimeInMillis(); | |
| DateFormat dateFormat = getDateInstance(); | |
| Log.i(LOG_TAG, "Range Start: " + dateFormat.format(startTime)); | |
| Log.i(LOG_TAG, "Range End: " + dateFormat.format(endTime)); | |
| DataSource ESTIMATED_STEP_DELTAS = new DataSource.Builder() | |
| .setDataType(DataType.TYPE_DISTANCE_DELTA) | |
| .setType(DataSource.TYPE_DERIVED) | |
| .setStreamName("merge_distance_delta") | |
| .setAppPackageName("com.google.android.gms") | |
| .build(); | |
| return 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(ESTIMATED_STEP_DELTAS, DataType.AGGREGATE_DISTANCE_DELTA) | |
| // 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(); | |
| } | |
| private static void postDataSetDistanceToAPI(DataSet dataSet, int daysIndex) { | |
| Log.i(LOG_TAG, "Data returned for Data type: " + dataSet.getDataType().getName()); | |
| if (dataSet.getDataPoints().isEmpty()) { | |
| Log.i(LOG_TAG, "No distance this day:"); | |
| Calendar calStart = new GregorianCalendar(); | |
| calStart.setTime(new Date()); | |
| calStart.add(Calendar.DAY_OF_YEAR, -(daysIndex + 1)); | |
| Date calStartTime = calStart.getTime(); | |
| String dateStr = DateUtils.formatDateAsString(calStartTime, "yyyy-MM-dd"); | |
| logActivitiesInThePastDate("distance", "0", dateStr); | |
| } else { | |
| for (DataPoint dp : dataSet.getDataPoints()) { | |
| Log.i(LOG_TAG, "Data point:"); | |
| Log.i(LOG_TAG, "\tType: " + dp.getDataType().getName()); | |
| long milliSeconds = dp.getStartTime(TimeUnit.MILLISECONDS); | |
| Calendar calendar = Calendar.getInstance(); | |
| calendar.setTimeInMillis(milliSeconds); | |
| String dateStr = DateUtils.formatDateAsString(calendar.getTime(), "yyyy-MM-dd"); | |
| for (Field field : dp.getDataType().getFields()) { // 4 times | |
| Value value = dp.getValue(field); | |
| Log.i(LOG_TAG, "\tField: " + field.getName() + | |
| " Value: " + value); | |
| // GOOGLE FIT | |
| float distance = Float.valueOf(value.toString()); | |
| String distanceStr = String.format("%.2f", distance / 1000); | |
| // Finally call to log-activity | |
| logActivitiesInThePastDate("distance", distanceStr, dateStr); | |
| } | |
| } | |
| } | |
| } | |
| private static void logActivitiesInThePastDate(String activityLabel, String value, String dateStr) { | |
| JsonObject jo = new JsonObject(); | |
| jo.addProperty("user_id", LocalSave.getUserId()); | |
| jo.addProperty("activity", activityLabel); | |
| jo.addProperty("value", value); | |
| jo.addProperty("date", dateStr); | |
| LogActivityAPI.postData(jo, new LogActivityAPI.ThisCallback() { | |
| @Override | |
| public void onSuccess(LogActivityResponse log) { | |
| if (log.getSuccess()) { | |
| Log.d(LOG_TAG, "Logged Past Activity successfully :)"); | |
| // don't update anything. Just logged and happy days :) | |
| } else { | |
| Log.e(LOG_TAG, "Logged Past Activity was NOT successful!!! :("); | |
| } | |
| } | |
| @Override | |
| public void onFailure() { | |
| Log.e(LOG_TAG, "onFailure"); | |
| } | |
| @Override | |
| public void onError(APIError error) { | |
| Log.e(LOG_TAG, "Exception: " + error.getErrorMessage()); | |
| } | |
| }); | |
| } | |
| public static void requestGoogleFitPermissionResult(int requestCode, @NonNull int[] grantResults, | |
| GoogleApiClient client, Activity activity) { | |
| Log.i(LOG_TAG, "onRequestPermissionsResult"); | |
| if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) { | |
| if (grantResults.length <= 0) { | |
| // If user interaction was interupted, the permission request is cancelled and you | |
| // receive empty arrays. | |
| Log.i(LOG_TAG, "User interaction was cancelled"); | |
| } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | |
| // Permission was granted | |
| buildFitnessClient(client, activity); | |
| } else { | |
| // Permission denied | |
| Toast.makeText(App.getInstance(), "Permission denied for Google FIT", Toast.LENGTH_LONG).show(); | |
| } | |
| } | |
| } | |
| private static void postBackgroundHistoryGoogleFit(int days) { | |
| Log.d(LOG_TAG, "The number of days Not Launched saved was: " + days); | |
| for (int i = 0; i <= days; i++) { | |
| if (checkPermissionsAccessFineLocation() && checkPermissionsBodySensor()) { | |
| new DataTaskDistanceBackgroundGoogleFit().execute(i); | |
| } | |
| } | |
| } | |
| public static class DataTaskDistanceBackgroundGoogleFit extends AsyncTask<Integer, Void, Void> { | |
| DataSet dataSet; | |
| int daysIndex; | |
| protected Void doInBackground(Integer... params) { | |
| daysIndex = params[0]; | |
| DataReadRequest dataReadRequest = getDataDistanceReadRequest(daysIndex); | |
| // Invoke the History API to fetch the data with the query and await the result of | |
| // the read request. | |
| DataReadResult dataReadResult = | |
| Fitness.HistoryApi.readData(client, dataReadRequest).await(1, TimeUnit.MINUTES); | |
| if (dataReadResult.getStatus().isSuccess()) { | |
| Log.d(LOG_TAG, " result: " + dataReadResult.toString()); | |
| } else { | |
| Log.d(LOG_TAG, "result: " + dataReadResult.toString()); | |
| } | |
| List<Bucket> bucketList = dataReadResult.getBuckets(); | |
| for (Bucket bucket : bucketList) { | |
| dataSet = (bucket.getDataSet(DataType.TYPE_DISTANCE_DELTA)); | |
| } | |
| return null; | |
| } | |
| @Override | |
| protected void onPostExecute(Void aVoid) { | |
| if (dataSet != null) { | |
| postDataSetDistanceToSpectrumAPI(dataSet, daysIndex); | |
| } | |
| } | |
| } | |
| public static class VerifyDataTaskDistance extends AsyncTask<GoogleApiClient, Void, Void> { | |
| float total = 0; | |
| protected Void doInBackground(GoogleApiClient... clients) { | |
| PendingResult<DailyTotalResult> result = Fitness.HistoryApi.readDailyTotal(clients[0], DataType.TYPE_DISTANCE_DELTA); | |
| DailyTotalResult totalResult = result.await(30, TimeUnit.SECONDS); | |
| if (totalResult.getStatus().isSuccess()) { | |
| DataSet totalSet = totalResult.getTotal(); | |
| if (totalSet != null) { | |
| total = totalSet.isEmpty() | |
| ? 0 | |
| : totalSet.getDataPoints().get(0).getValue(Field.FIELD_DISTANCE).asFloat(); | |
| } | |
| } else { | |
| Log.e(LOG_TAG, "There was a problem getting the distance count"); | |
| } | |
| Log.i(LOG_TAG, "Total distance: " + total); | |
| return null; | |
| } | |
| @Override | |
| protected void onPostExecute(Void aVoid) { | |
| // UI | |
| updateDistanceGoogle(total, MainActivity.mainActivity.getSupportFragmentManager()); | |
| } | |
| } | |
| private static void readDistanceToday() { | |
| new VerifyDataTaskDistance().execute(client); | |
| } | |
| // ----------- Google Fit Daily DISTANCE ----------- | |
| public static void subscribeDailyDistance() { | |
| Log.d(LOG_TAG, "subscribeDailyDistance was called"); | |
| if (client != null) { | |
| // To create a subscription, invoke the Recording API. | |
| // As soon as the subscription is active, fitness data will start recording | |
| Fitness.RecordingApi.subscribe(client, DataType.TYPE_DISTANCE_DELTA) | |
| .setResultCallback(new ResultCallback<Status>() { | |
| @Override | |
| public void onResult(@NonNull Status status) { | |
| if (status.isSuccess()) { | |
| if (status.getStatusCode() == FitnessStatusCodes.SUCCESS_ALREADY_SUBSCRIBED) { | |
| Log.d(LOG_TAG, "Existing subscription for activity detected."); | |
| } else { | |
| Log.d(LOG_TAG, "Successfully subscribed"); | |
| } | |
| // :) | |
| readDistanceToday(); | |
| } else { | |
| Log.e(LOG_TAG, "There was a problem subscribing"); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment