Skip to content

Instantly share code, notes, and snippets.

@Splaktar
Created February 13, 2015 22:42
Show Gist options
  • Save Splaktar/8c955d31bc5db42e64fe to your computer and use it in GitHub Desktop.
Save Splaktar/8c955d31bc5db42e64fe to your computer and use it in GitHub Desktop.
Using the Google Fit API via Google Play Services on Android
package com.devintent.fitness.app;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.TimePickerDialog;
import android.content.Intent;
import android.content.IntentSender;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
import android.widget.Toast;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.fitness.Fitness;
import com.google.android.gms.fitness.FitnessActivities;
import com.google.android.gms.fitness.data.Session;
import com.google.android.gms.fitness.request.SessionInsertRequest;
import com.google.common.base.Strings;
import net.danlew.android.joda.JodaTimeAndroid;
import org.joda.time.DateTime;
import java.util.concurrent.TimeUnit;
import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;
public class LogSessionActivity extends Activity {
private static final String TAG = "LogSessionActivity";
/**
* Track whether an authorization activity is stacking over the current activity, i.e. when
* a known auth error is being resolved, such as showing the account chooser or presenting a
* consent dialog. This avoids common duplications as might happen on screen rotations, etc.
*/
private static final String AUTH_PENDING = "auth_state_pending";
private static final int REQUEST_OAUTH = 1;
private boolean authInProgress = false;
private GoogleApiClient mClient = null;
@InjectView(R.id.date)
EditText dateView;
@InjectView(R.id.time)
EditText timeView;
@InjectView(R.id.hours)
EditText hoursView;
@InjectView(R.id.minutes)
EditText minutesView;
MenuItem saveSession;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_session);
ButterKnife.inject(this);
JodaTimeAndroid.init(this);
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING);
}
buildFitnessClient();
}
@Override
protected void onStart() {
super.onStart();
// Get an Analytics tracker to report app starts and uncaught exceptions etc.
GoogleAnalytics.getInstance(this).reportActivityStart(this);
// Connect to the Fitness API
Log.i(TAG, "Connecting to Fitness API...");
mClient.connect();
DateTime now = new DateTime();
dateView.setText(Util.formatDate(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth()));
DateTime twoHoursAgo = now.minusHours(2);
timeView.setText(Util.formatTime(twoHoursAgo.getHourOfDay(), 0, this));
minutesView.requestFocus();
}
@Override
protected void onStop() {
super.onStop();
// Stop the analytics tracking
GoogleAnalytics.getInstance(this).reportActivityStop(this);
// Disconnect from the Fitness API.
if (mClient.isConnected()) {
mClient.disconnect();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "Processing onActivityResult...");
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AUTH_PENDING, authInProgress);
}
/**
* Build a {@link GoogleApiClient} that will authenticate the user and allow the application
* to connect to Fitness APIs. The scopes included should match the scopes your app needs
* (see documentation for details). Authentication will occasionally fail intentionally,
* and in those cases, there will be a known resolution, which the OnConnectionFailedListener()
* can address. Examples of this include the user never having signed in before, or having
* multiple accounts on the device and needing to specify which account to use, etc.
*/
private void buildFitnessClient() {
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.API)
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ))
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected to Fitness API!!!");
// Now you can make calls to the Fitness APIs.
// Put application specific code here.
if (saveSession != null) {
saveSession.setEnabled(true);
}
}
@Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
}
)
.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
LogSessionActivity.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(LogSessionActivity.this, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception while starting resolution activity", e);
}
}
}
}
)
.build();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_log_session, menu);
saveSession = menu.findItem(R.id.save_session);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.save_session) {
saveSession();
return true;
} else if (id == R.id.cancel_session) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@OnClick(R.id.time)
public void timeEntryClicked() {
DialogFragment newFragment = new TimePickerFragment();
newFragment.show(this.getFragmentManager(), "timePicker");
}
@OnClick(R.id.date)
public void dateEntryClicked() {
DialogFragment newFragment = new DatePickerFragment();
newFragment.show(this.getFragmentManager(), "datePicker");
}
@SuppressLint("ValidFragment")
public class TimePickerFragment extends DialogFragment
implements TimePickerDialog.OnTimeSetListener
{
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DateTime time;
String timeStr = timeView.getText().toString();
if (Strings.isNullOrEmpty(timeStr)) {
time = new DateTime();
} else {
time = Util.parseTime(timeStr, LogSessionActivity.this);
}
// Create a new instance of TimePickerDialog and return it
return new TimePickerDialog(getActivity(), this, time.getHourOfDay(),
time.getMinuteOfHour(), DateFormat.is24HourFormat(LogSessionActivity.this));
}
public void onTimeSet(TimePicker dialog, int hourOfDay, int minute) {
// Do something with the time chosen by the user
timeView.setText(Util.formatTime(hourOfDay, minute, LogSessionActivity.this));
}
}
@SuppressLint("ValidFragment")
public class DatePickerFragment extends DialogFragment
implements DatePickerDialog.OnDateSetListener {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DateTime date = Util.parseDate(dateView.getText().toString());
// Create a new instance of DatePickerDialog and return it
return new DatePickerDialog(getActivity(), this, date.getYear(), date.getMonthOfYear()-1, date.getDayOfMonth());
}
public void onDateSet(DatePicker view, int year, int month, int day) {
// Do something with the date chosen by the user
dateView.setText(Util.formatDate(year, month+1, day));
}
}
private void saveSession() {
SaveSessionTask saveSessionTask = new SaveSessionTask();
CharSequence msg = "Saving surf session to Google Fit...";
Log.i(TAG, msg.toString());
Toast.makeText(LogSessionActivity.this, msg, Toast.LENGTH_SHORT).show();
saveSessionTask.execute();
}
class SaveSessionTask extends AsyncTask<Void, Void, com.google.android.gms.common.api.Status> {
protected void onPreExecute() {}
protected com.google.android.gms.common.api.Status doInBackground(Void... unused) {
DateTime startTime = Util.parseTime(timeView.getText().toString(), LogSessionActivity.this);
long startTimeMillis = startTime.getMillis();
String minutes = minutesView.getText().toString();
String hours = hoursView.getText().toString();
DateTime endTime = startTime;
if (!Strings.isNullOrEmpty(minutes)) {
endTime = endTime.plusMinutes(Integer.valueOf(minutes));
}
if (!Strings.isNullOrEmpty(hours)) {
endTime = endTime.plusHours(Integer.valueOf(hours));
}
// Create a session with metadata about the activity.
Session session = new Session.Builder()
.setName("Surf Session by DevIntent")
.setDescription("Surf Session")
.setActivity(FitnessActivities.SURFING)
.setStartTime(startTimeMillis, TimeUnit.MILLISECONDS)
.setEndTime(endTime.getMillis(), TimeUnit.MILLISECONDS)
.build();
// Build a session insert request
SessionInsertRequest insertRequest = new SessionInsertRequest.Builder()
.setSession(session).build();
// Then, invoke the Sessions API to insert the session and await the result,
// which is possible here because of the AsyncTask. Always include a timeout when
// calling await() to avoid hanging that can occur from the service being shutdown
// because of low memory or other conditions.
Log.d(TAG, "Inserting the surf session via the Google Fit History API...");
return Fitness.SessionsApi.insertSession(mClient, insertRequest)
.await(1, TimeUnit.MINUTES);
}
@Override
protected void onPostExecute(com.google.android.gms.common.api.Status status) {
// Before querying the session, check to see if the insertion succeeded.
if (!status.isSuccess()) {
CharSequence msg = "There was a problem saving the session: " +
status.getStatusMessage();
Log.i(TAG, msg.toString());
Toast.makeText(LogSessionActivity.this, msg, Toast.LENGTH_LONG).show();
} else {
// At this point, the session has been inserted and can be read.
CharSequence msg = "Surf session saved to Google Fit!";
Log.i(TAG, msg.toString());
Toast.makeText(LogSessionActivity.this, msg, Toast.LENGTH_SHORT).show();
finish();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment