User motion detector
Description
The following code shows how to recongize user motion.
Code revised from
Android Recipes:A Problem-Solution Approach
http://www.apress.com/9781430234135
ISBN13: 978-1-4302-3413-5
Example
Register permission for ACTIVITY_RECOGNITION.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.androidrecipes.usermotionactivity"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.androidrecipes.usermotionactivity.MainActivity"
android:label="User Activity"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.androidrecipes.usermotionactivity.UserMotionService" />
</application>
</manifest>
Main layout xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true"
android:transcriptMode="normal" />
<TextView
android:id="@+id/blocker"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:clickable="true"
android:textSize="32dp"
android:textColor="#F55"
android:text="It is not safe to operate your mobile device while in a vehicle!"
android:background="#C333"
android:visibility="gone" />
</FrameLayout>
Java code for Main activity
package com.androidrecipes.usermotionactivity;
/*from w ww . j a v a2 s.com*/
import com.androidrecipes.usermotionactivity.UserMotionService.LocalBinder;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.ActivityRecognitionClient;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements
ServiceConnection,
UserMotionService.OnActivityChangedListener,
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener {
private static final String TAG = "UserActivity";
private Intent mServiceIntent;
private PendingIntent mCallbackIntent;
private UserMotionService mService;
private ActivityRecognitionClient mRecognitionClient;
//Custom list adapter to display our results
private ActivityAdapter mListAdapter;
private View mBlockingView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBlockingView = findViewById(R.id.blocker);
//Construct a simple list adapter that will display all the
// incoming activity change events from the service.
ListView list = (ListView) findViewById(R.id.list);
mListAdapter = new ActivityAdapter(this);
list.setAdapter(mListAdapter);
//When the list is clicked, display all the probable activities
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
showDetails(mListAdapter.getItem(position));
}
});
//Check if Google Play Services is up to date.
switch (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this)) {
case ConnectionResult.SUCCESS:
//Do nothing, move on
break;
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
Toast.makeText(this,
"Activity service requires an update, please open Google Play.",
Toast.LENGTH_SHORT).show();
finish();
return;
default:
Toast.makeText(this,
"Activity service is not available on this device.",
Toast.LENGTH_SHORT).show();
finish();
return;
}
//Create a client instance for talking to Google Services
mRecognitionClient = new ActivityRecognitionClient(this, this, this);
//Create an Intent to bind to the service
mServiceIntent = new Intent(this, UserMotionService.class);
//Create a PendingIntent that Google Services will use for callbacks
mCallbackIntent = PendingIntent.getService(this, 0,
mServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
protected void onResume() {
super.onResume();
//Connect to Google Services and our Service
mRecognitionClient.connect();
bindService(mServiceIntent, this, BIND_AUTO_CREATE);
}
@Override
protected void onPause() {
super.onPause();
//Disconnect from all services
mRecognitionClient.removeActivityUpdates(mCallbackIntent);
mRecognitionClient.disconnect();
disconnectService();
unbindService(this);
}
/** ServiceConnection Methods */
public void onServiceConnected(ComponentName name, IBinder service) {
//Attach ourselves to our Service as a callback for events
mService = ((LocalBinder) service).getService();
mService.setOnActivityChangedListener(this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
disconnectService();
}
private void disconnectService() {
if (mService != null) {
mService.setOnActivityChangedListener(null);
}
mService = null;
}
/** Google Services Connection Callbacks */
@Override
public void onConnected(Bundle connectionHint) {
//We must wait until the services are connected
// to request any updates.
mRecognitionClient.requestActivityUpdates(5000, mCallbackIntent);
}
@Override
public void onDisconnected() {
Log.w(TAG, "Google Services Disconnected");
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.w(TAG, "Google Services Connection Failure");
}
/** OnActivityChangedListener Methods */
@Override
public void onUserActivityChanged(int bestChoice, int bestConfidence,
ActivityRecognitionResult newActivity) {
//Add latest event to the list
mListAdapter.add(newActivity);
mListAdapter.notifyDataSetChanged();
//Determine user action based on our custom algorithm
switch (bestChoice) {
case DetectedActivity.IN_VEHICLE:
case DetectedActivity.ON_BICYCLE:
mBlockingView.setVisibility(View.VISIBLE);
break;
case DetectedActivity.ON_FOOT:
case DetectedActivity.STILL:
mBlockingView.setVisibility(View.GONE);
break;
default:
//Ignore other states
break;
}
}
/*
* Utility that builds a simple Toast with all the probable
* activity choices with their confidence values
*/
private void showDetails(ActivityRecognitionResult activity) {
StringBuilder sb = new StringBuilder();
sb.append("Details:");
for(DetectedActivity element : activity.getProbableActivities()) {
sb.append("\n"+UserMotionService.getActivityName(element)
+ ", " + element.getConfidence() + "% sure");
}
Toast.makeText(this, sb.toString(), Toast.LENGTH_SHORT).show();
}
/*
* ListAdapter to display each activity result we receive from the service
*/
private static class ActivityAdapter extends ArrayAdapter<ActivityRecognitionResult> {
public ActivityAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext())
.inflate(android.R.layout.simple_list_item_1, parent, false);
}
//Display the most probable activity with its confidence in the list
TextView tv = (TextView) convertView;
ActivityRecognitionResult result = getItem(position);
DetectedActivity newActivity = result.getMostProbableActivity();
String entry = DateFormat.format("hh:mm:ss", result.getTime())
+ ": " + UserMotionService.getActivityName(newActivity) + ", "
+ newActivity.getConfidence() + "% confidence";
tv.setText(entry);
return convertView;
}
}
}
User motion service
// ww w .j ava 2s. c o m
package com.androidrecipes.usermotionactivity;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
import android.app.IntentService;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
public class UserMotionService extends IntentService {
private static final String TAG = "UserMotionService";
/*
* Callback interface for detected activity type changes
*/
public interface OnActivityChangedListener{
public void onUserActivityChanged(int bestChoice, int bestConfidence,
ActivityRecognitionResult newActivity);
}
/* Last detected activity type */
private DetectedActivity mLastKnownActivity;
/*
* Marshals requests from the background thread so the callbacks
* can be made on the main (UI) thread.
*/
private CallbackHandler mHandler;
private static class CallbackHandler extends Handler {
/* Callback for activity changes */
private OnActivityChangedListener mCallback;
public void setCallback(OnActivityChangedListener callback) {
mCallback = callback;
}
@Override
public void handleMessage(Message msg) {
if (mCallback != null) {
//Read payload data out of the message and fire callback
ActivityRecognitionResult newActivity = (ActivityRecognitionResult) msg.obj;
mCallback.onUserActivityChanged(msg.arg1, msg.arg2, newActivity);
}
}
}
public UserMotionService() {
//String is used to name the background thread created
super("UserMotionService");
mHandler = new CallbackHandler();
}
public void setOnActivityChangedListener(OnActivityChangedListener listener) {
mHandler.setCallback(listener);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.w(TAG, "Service is stopping...");
}
/*
* Incoming action events from the framework will come
* in here. This is called on a background thread, so
* we can do long processing here if we wish.
*/
@Override
protected void onHandleIntent(Intent intent) {
if (ActivityRecognitionResult.hasResult(intent)) {
//Extract the result from the Intent
ActivityRecognitionResult result =
ActivityRecognitionResult.extractResult(intent);
DetectedActivity activity = result.getMostProbableActivity();
Log.v(TAG, "New User Activity Event");
//If the highest probability is UNKNOWN, but the confidence is low,
// check if another exists and select it instead
if (activity.getType() == DetectedActivity.UNKNOWN
&& activity.getConfidence() < 60
&& result.getProbableActivities().size() > 1) {
//Select the next probable element
activity = result.getProbableActivities().get(1);
}
//On a change in activity, alert the callback
if (mLastKnownActivity == null
|| mLastKnownActivity.getType() != activity.getType()
|| mLastKnownActivity.getConfidence() != activity.getConfidence()) {
//Pass the results to the main thread inside a Message
Message msg = Message.obtain(null,
0, //what
activity.getType(), //arg1
activity.getConfidence(), //arg2
result);
mHandler.sendMessage(msg);
}
mLastKnownActivity = activity;
}
}
/*
* This is called when the Activity wants to bind to the
* service. We have to provide a wrapper around this instance
* to pass it back.
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/*
* This is a simple wrapper that we can pass to the Activity
* to allow it direct access to this service.
*/
private LocalBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public UserMotionService getService() {
return UserMotionService.this;
}
}
/*
* Utility to get a good display name for each state
*/
public static String getActivityName(DetectedActivity activity) {
switch(activity.getType()) {
case DetectedActivity.IN_VEHICLE:
return "Driving";
case DetectedActivity.ON_BICYCLE:
return "Biking";
case DetectedActivity.ON_FOOT:
return "Walking";
case DetectedActivity.STILL:
return "Not Moving";
case DetectedActivity.TILTING:
return "Tilting";
case DetectedActivity.UNKNOWN:
default:
return "No Clue";
}
}
}