Android Activity Recognition

Activity Recognition gives our Android device the ability to detect a number of our physical activities like walking, riding a bicycle, driving a car or standing idle. All that by simply using an API to access Google Play Services, an increasingly crucial piece of software available to all Android versions.

As in the article on geofencing, we will download the sample app (ActivityRecognition.zip) at the Android developer’s site and start playing with it, eventually modifying parts of it to fit our purposes. We will show here only the most relevant code sections.

The first thing to note is that we need a specific permission to use Activity Recognition:

<!-- Inside manifest file -->
 <uses-permission
    android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>

As with geofencing or location updates, we use the API to request Google Play Services to analyse our data and provide us with the results. The chain of method calls for requesting updates is similar to that of geofencing:

  1. Make sure that Google Play Services is available
  2. As an Activity Recognition client, request a connection
  3. Once connected, Location Services calls back the onConnected() method in our app
  4. Proceed with the updates request via a Pending Intent pointing to an IntentService we have written
  5. Google Location Services sends out its activity recognition updates as Intent objects, using the PendingIntent we provided. Get & process the updates in our IntentService’s onHandleIntent() method.

The sample app writes all the updates in a log file, and that is OK if we like that sort of thing…though a closer look at the data makes us realize that most of it is garbage. Do we really need to know that we have a 27% chance of being driving a vehicle and a 7% chance of riding a bicycle when we are in fact sitting idle at our desk? Not really. What we want is the most significant data, and in this case, that would be the most probable activity:

//..
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;

/**
 * Service that receives ActivityRecognition updates. It receives updates
 * in the background, even if the main Activity is not visible.
 */
public class ActivityRecognitionIntentService extends IntentService {  
  //..
  /**
     * Called when a new activity detection update is available.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
         //...
          // If the intent contains an update
        if (ActivityRecognitionResult.hasResult(intent)) {
            // Get the update
            ActivityRecognitionResult result = 
              ActivityRecognitionResult.extractResult(intent);

             DetectedActivity mostProbableActivity 
                    = result.getMostProbableActivity();

             // Get the confidence % (probability)
            int confidence = mostProbableActivity.getConfidence();
 
            // Get the type 
            int activityType = mostProbableActivity.getType();
           /* types:
            * DetectedActivity.IN_VEHICLE
            * DetectedActivity.ON_BICYCLE
            * DetectedActivity.ON_FOOT
            * DetectedActivity.STILL
            * DetectedActivity.UNKNOWN
            * DetectedActivity.TILTING
            */
            // process
        }
    }
}

Instead of writing the updates to a log file, it is simpler to just store them in memory (e.g. in a static List in a dedicated class) and display them to the user of our app. One way to do this would be by using a Fragment to display the updates on top of a Google map. As commented in previous articles, Fragments were introduced in Honeycomb but are also available to older Android versions through the Support Library.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/lay_map"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical">

<fragment 
 android:id="@+id/map"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 class="com.google.android.gms.maps.SupportMapFragment"/>

<!-- activity recognition -->
<fragment
 android:id="@+id/ar_fragment"
 android:name="package.to.our.fragment.ActReconFragment"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"/>
    
</FrameLayout>

Once we define our own XML layout for the ActReconFragment and give it a transparent background (left to the reader as an exercise), we will get a nice overlaid display like this:

pic1

Since we have chosen to show the most probable activity to the users of our app, we need the display to be dynamic, like a live feed. For that, we can add a local broadcast in our service:

 //inside ActivityRecognitionIntentService 's onHandleIntent
Intent broadcastIntent = new Intent();
    	
// Give it the category for all intents sent by the Intent Service
broadcastIntent.addCategory(ActivityUtils.CATEGORY_LOCATION_SERVICES);
// Set the action and content for the broadcast intent
broadcastIntent.setAction(ActivityUtils.ACTION_REFRESH_STATUS_LIST);

// Broadcast *locally* to other components in this app
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);

We are using a LocalBroadcastManager (included in android 3.0 and above and in the support library v4 for early releases). Apart from providing our own layout to position the Activity Detection panel on top of a map, the only new code snippet we wrote is the above local broadcast. For the remainder below, we have simply re-positioned the sample app’s code in a fragment and use in-memory storage of the activity updates instead of using a log file. The receiver on that local broadcast is in our Fragment:

//...
public class ActReconFragment extends Fragment{

 // Intent filter for incoming broadcasts from the IntentService
 IntentFilter mBroadcastFilter;

 // Instance of a local broadcast manager
 private LocalBroadcastManager mBroadcastManager;

//...
/**
 * Called when the corresponding Map Activity's
 * onCreate() method has completed.
 */
 @Override
public void onActivityCreated(Bundle savedInstanceState) {
   super.onActivityCreated(savedInstanceState);		 
		 
  // Set the broadcast receiver intent filer
  mBroadcastManager = LocalBroadcastManager.getInstance(getActivity());

  // Create a new Intent filter for the broadcast receiver
  mBroadcastFilter = new IntentFilter(ActivityUtils.ACTION_REFRESH_STATUS_LIST);
  mBroadcastFilter.addCategory(ActivityUtils.CATEGORY_LOCATION_SERVICES);

  //...		 
}

/**
* Broadcast receiver that receives activity update intents
* This receiver is local only. It can't read broadcast Intents from other apps.
*/
BroadcastReceiver updateListReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
     // When an Intent is received from the update listener IntentService, 
     // update the display.
     updateActivityHistory();
   }
};
//...
}

Live feed shots:

pic2

Once we have taken care of the display, we need to move on to other important aspects like what to do with those activity updates. The sample app gives us one example of that in the ActivityRecognitionIntentService:


if( 
   // If the current type is "moving" i.e on foot, bicycle or vehicle
   isMoving(activityType)
   &&
  // The activity has changed from the previous activity
  activityChanged(activityType)
  // The confidence level for the current activity is >= 50%
  && (confidence >= 50)) {

  // do something useful
}

Simply getting the most probable activity might be OK for displaying purposes, but might not be enough for an app to act on it and do something useful. We need to make sure that the type of activity and the corresponding confidence level (i.e. probability) are adequate for our purposes. While a detected activity type of “Unknown” with a confidence level of 52% is next to useless, knowing that the user is moving in a vehicle as opposed to walking can be put to good use: increase the frequency of location updates, enlarge the map area of available points of interest, etc…

Activity Recognition has been added as an experimental feature to this Geofencing app. Check it out and feel free to post any feedback.

This article is also available at DZone and Java Code Geeks.

About these ads

,

  1. #1 by ah on February 8, 2014 - 2:37 pm

    can you post the complete source code as a project, please it will help me

    thank you

  2. #2 by Rana on February 10, 2014 - 7:41 am

    can you please send me the source code, i would really appreciate that. thanks in advance

  3. #3 by Randa on February 10, 2014 - 7:51 am

    oh that’s great that exactly what I’m working on
    but I get stuck it seems that some code is missing
    could you kindly share what you did

  4. #4 by Mike Jancola on March 23, 2014 - 7:05 pm

    Anyone have success with adding valuable logic on top of the API? I’ve been playing with it for a while now and it’s really inconsistent. Specifically, the api sometimes returns incorrect motions (with over 90% accuracy) for over 20 seconds. I’m struggling how to determine whether to ‘trust’ the detected activity.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: