I am using the new APIs for locations and trying the ActivityRecognition. I used the code base that its on the Google Developers Training and everything went just fine until I wanted to update the UI. (I'm aware that you cannot update the UI directly on a service and that's why I'm using a handler/interface/something.) What I tried:
Using an interface, but this was useless since I didn't find a way to get the Activity context from the IntentService.
Also tried passing a handler to the IntentService for it to send a message with the action but for some reason whenever it reaches the call to this:
// If the intent contains an update
if (ActivityRecognitionResult.hasResult(intent)) {
this just returns false since I'm passing the messenger with the handler. If I remove the line that add the extra containing the message it works just fine but I cannot update my UI.
I tried removing the extra before starting the service but no success. Any Ideas how should I fix this?
These are the codes I have:
Activity:
public class MainActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener {
public static final int MILLISECONDS_PER_SECOND = 1000;
public static final int DETECTION_INTERVAL_SECONDS = 20;
public static final int DETECTION_INTERVAL_MILLISECONDS = MILLISECONDS_PER_SECOND * DETECTION_INTERVAL_SECONDS;
private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
private PendingIntent mActivityRecognitionPendingIntent;
// Store the current activity recognition client
private ActivityRecognitionClient mActivityRecognitionClient;
private Context mContext;
// Flag that indicates if a request is underway.
private boolean mInProgress;
private static final String TAG = "MainActivity";
public static final String KEY_ACTION = "user_action";
public enum REQUEST_TYPE {START, STOP}
private REQUEST_TYPE mRequestType;
private TextView tvAction;
//FIXME I'm aware of this potential leak
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Bundle reply = msg.getData();
final String userAction = reply.getString(KEY_ACTION);
tvAction.setText("You are now: "+userAction);
// do whatever with the bundle here
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
tvAction = (TextView)findViewById(R.id.tv_action);
// Start with the request flag set to false
mInProgress = false;
mActivityRecognitionClient =
new ActivityRecognitionClient(mContext, this, this);
Intent intent = new Intent(
mContext, ActivityRecognitionIntentService.class);
//putting this will make return false.
intent.putExtra("messenger", new Messenger(handler));
mActivityRecognitionPendingIntent =
PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
private void startUpdates() {
mRequestType = REQUEST_TYPE.START;
// Check for Google Play services
if (!servicesConnected()) {
return;
}
// If a request is not already underway
if (!mInProgress) {
// Indicate that a request is in progress
mInProgress = true;
// Request a connection to Location Services
mActivityRecognitionClient.connect();
//
}
}
private void stopUpdates() {
// Set the request type to STOP
mRequestType = REQUEST_TYPE.STOP;
/*
* Test for Google Play services after setting the request type.
* If Google Play services isn't present, the request can be
* restarted.
*/
if (!servicesConnected()) {
return;
}
// If a request is not already underway
if (!mInProgress) {
// Indicate that a request is in progress
mInProgress = true;
// Request a connection to Location Services
mActivityRecognitionClient.connect();
//
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Decide what to do based on the original request code
switch (requestCode) {
case CONNECTION_FAILURE_RESOLUTION_REQUEST:
/*
* If the result code is Activity.RESULT_OK, try to connect again
*/
switch (resultCode) {
case Activity.RESULT_OK:
/*
* Try the request again
*/
break;
}
}
}
private boolean servicesConnected() {
// Check that Google Play services is available
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
// In debug mode, log the status
Log.d("Activity Recognition", "Google Play services is available.");
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(getSupportFragmentManager(), "Activity Recognition");
}
return false;
}
}
// Define a DialogFragment that displays the error dialog
public static class ErrorDialogFragment extends DialogFragment {
// Global field to contain the error dialog
private Dialog mDialog;
// Default constructor. Sets the dialog field to null
public ErrorDialogFragment() {
super();
mDialog = null;
}
// Set the dialog to display
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
// Return a Dialog to the DialogFragment.
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Turn off the request flag
mInProgress = false;
/*
* If the error has a resolution, start a Google Play services
* activity to resolve it.
*/
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (SendIntentException e) {
// Log the error
e.printStackTrace();
}
// If no resolution is available, display an error dialog
} else {
// Get the error code
int errorCode = connectionResult.getErrorCode();
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
errorCode,
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
ErrorDialogFragment errorFragment =
new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(
getSupportFragmentManager(),
"Activity Recognition");
}
}
}
@Override
public void onConnected(Bundle arg0) {
switch (mRequestType) {
case START:
/*
* Request activity recognition updates using the preset
* detection interval and PendingIntent. This call is
* synchronous.
*/
mActivityRecognitionClient.requestActivityUpdates(
DETECTION_INTERVAL_MILLISECONDS,
mActivityRecognitionPendingIntent);
break;
case STOP :
mActivityRecognitionClient.removeActivityUpdates(
mActivityRecognitionPendingIntent);
break;
default :
Log.e(TAG, "Unknown request type in onConnected().");
break;
}
/*
* Since the preceding call is synchronous, turn off the
* in progress flag and disconnect the client
*/
mInProgress = false;
mActivityRecognitionClient.disconnect();
}
@Override
public void onDisconnected() {
// Turn off the request flag
mInProgress = false;
// Delete the client
mActivityRecognitionClient = null;
}
@Override
protected void onPause() {
super.onPause();
stopUpdates();
}
@Override
protected void onResume() {
super.onResume();
startUpdates();
}
@Override
protected void onStop() {
super.onStop();
stopUpdates();
}
}
IntentService
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
public class ActivityRecognitionIntentService extends IntentService {
// Formats the timestamp in the log
private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss.SSSZ";
private static final String TAG = "ActivityRecognitionIntentService";
private static final String SHARED_PREFERENCES = "ActivityRecognition";
private static final String KEY_PREVIOUS_ACTIVITY_TYPE = "KEY_PREVIOUS_ACTIVITY_TYPE";
// A date formatter
private SimpleDateFormat mDateFormat;
// Store the app's shared preferences repository
private SharedPreferences mPrefs;
protected Messenger messenger;
public ActivityRecognitionIntentService() {
// Set the label for the service's background thread
super("ActivityRecognitionIntentService");
}
public ActivityRecognitionIntentService(String name) {
super(name);
}
@Override
public void onStart(Intent intent, int startId) {
Bundle extras = intent.getExtras();
messenger = (Messenger) extras.get("messenger");
intent.removeExtra("messenger");
super.onStart(intent, startId);
}
/**
* Called when a new activity detection update is available.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Get a handle to the repository
Context mContext = getApplicationContext();
mPrefs = mContext.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
// Get a date formatter, and catch errors in the returned timestamp
try {
mDateFormat = (SimpleDateFormat) DateFormat.getDateTimeInstance();
} catch (Exception e) {
Log.e(TAG, getString(R.string.date_format_error));
}
// Format the timestamp according to the pattern, then localize the
// pattern
mDateFormat.applyPattern(DATE_FORMAT_PATTERN);
mDateFormat.applyLocalizedPattern(mDateFormat.toLocalizedPattern());
// If the intent contains an update
if (ActivityRecognitionResult.hasResult(intent)) {
// Get the update
ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
// Get the most probable activity from the list of activities in the
// update
DetectedActivity mostProbableActivity = result.getMostProbableActivity();
// Get the confidence percentage for the most probable activity
int confidence = mostProbableActivity.getConfidence();
// Get the type of activity
int activityType = mostProbableActivity.getType();
// Check to see if the repository contains a previous activity
if (!mPrefs.contains(KEY_PREVIOUS_ACTIVITY_TYPE)) {
// This is the first type an activity has been detected. Store
// the type
Editor editor = mPrefs.edit();
editor.putInt(KEY_PREVIOUS_ACTIVITY_TYPE, activityType);
editor.commit();
// If the repository contains a type
} else if (
// The confidence level for the current activity is > 50%
(confidence >= 50)) {
// Notify the user
final String userAction = getNameFromType(activityType);
sendNotification(userAction);
if (messenger != null) {
Message msg = Message.obtain();
Bundle data = new Bundle();
data.putString(MainActivity.KEY_ACTION, userAction);
msg.setData(data); //put the data here
try {
messenger.send(msg);
} catch (RemoteException e) {
Log.i("error", "error");
}
}
}
}
}
/**
* Post a notification to the user. The notification prompts the user to
* click it to open the device's GPS settings
*/
private void sendNotification(String type) {
// Create a notification builder that's compatible with platforms >=
// version 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext());
// Set the title, text, and icon
builder.setContentTitle(getString(R.string.app_name)).setContentText(type)// getString(R.string.turn_on_GPS))
.setSmallIcon(R.drawable.ic_launcher)
// Get the Intent that starts the Location settings panel
.setContentIntent(getContentIntent());
// Get an instance of the Notification Manager
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Build the notification and post it
notifyManager.notify(0, builder.build());
}
/**
* Get a content Intent for the notification
*
* @return A PendingIntent that starts the device's Location Settings panel.
*/
private PendingIntent getContentIntent() {
// Set the Intent action to open Location Settings
Intent gpsIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
// Create a PendingIntent to start an Activity
return PendingIntent.getActivity(getApplicationContext(), 0, gpsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Map detected activity types to strings
*
* @param activityType
* The detected activity type
* @return A user-readable name for the type
*/
private String getNameFromType(int activityType) {
switch (activityType) {
case DetectedActivity.IN_VEHICLE:
return "in_vehicle";
case DetectedActivity.ON_BICYCLE:
return "on_bicycle";
case DetectedActivity.ON_FOOT:
return "on_foot";
case DetectedActivity.STILL:
return "still";
case DetectedActivity.UNKNOWN:
return "unknown";
case DetectedActivity.TILTING:
return "tilting";
}
return "unknown";
}
}