Created
February 13, 2015 22:42
-
-
Save Splaktar/8c955d31bc5db42e64fe to your computer and use it in GitHub Desktop.
Using the Google Fit API via Google Play Services on Android
This file contains 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
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