From aa3d170e1fdebfe3486f4dbcf01fead581ac5ed3 Mon Sep 17 00:00:00 2001 From: Jimmy Shih Date: Wed, 22 May 2013 17:42:51 -0700 Subject: [PATCH] Add activity recognition. --- MyTracks/AndroidManifest.xml | 4 + MyTracks/res/values/keys.xml | 1 + .../io/backup/PreferenceBackupHelper.java | 1 + .../ActivityRecognitionIntentService.java | 92 +++++++++++++++++++ .../services/TrackRecordingService.java | 67 +++++++++++++- .../apps/mytracks/util/PreferencesUtils.java | 3 + .../apps/mytracks/util/TrackIconUtils.java | 6 +- 7 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/services/ActivityRecognitionIntentService.java diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 699528160..57b22400a 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -26,6 +26,8 @@ limitations under the License. + + @@ -243,6 +245,8 @@ limitations under the License. android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" /> + diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml index 731da2ad3..3d01f0162 100644 --- a/MyTracks/res/values/keys.xml +++ b/MyTracks/res/values/keys.xml @@ -65,6 +65,7 @@ limitations under the License. trackWidgetItem4 voiceFrequency + activityRecognitionType autoResumeTrackCurrentRetry driveDeletedList driveLargestChangeId diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java index 8be9c86ff..d6d8eeef7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java @@ -44,6 +44,7 @@ public class PreferenceBackupHelper { public PreferenceBackupHelper(Context context) { doNotBackup = new HashSet(); + doNotBackup.add(context.getString(R.string.activity_recognition_type_key)); doNotBackup.add(context.getString(R.string.auto_resume_track_current_retry_key)); doNotBackup.add(context.getString(R.string.drive_deleted_list_key)); doNotBackup.add(context.getString(R.string.drive_largest_change_id_key)); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/ActivityRecognitionIntentService.java b/MyTracks/src/com/google/android/apps/mytracks/services/ActivityRecognitionIntentService.java new file mode 100644 index 000000000..bde919d25 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/ActivityRecognitionIntentService.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.android.apps.mytracks.services; + +import com.google.android.apps.mytracks.util.PreferencesUtils; +import com.google.android.gms.location.ActivityRecognitionResult; +import com.google.android.gms.location.DetectedActivity; +import com.google.android.maps.mytracks.R; + +import android.app.IntentService; +import android.content.Intent; + +/** + * Service that receives ActivityRecognition updates. + * + * @author Jimmy Shih + */ +public class ActivityRecognitionIntentService extends IntentService { + + private static final int REQUIRED_CONFIDENCE = 90; + + public ActivityRecognitionIntentService() { + super(ActivityRecognitionIntentService.class.getSimpleName()); + } + + @Override + protected void onHandleIntent(Intent intent) { + if (!ActivityRecognitionResult.hasResult(intent)) { + return; + } + ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent); + DetectedActivity detectedActivity = result.getMostProbableActivity(); + if (detectedActivity == null) { + return; + } + if (detectedActivity.getConfidence() < REQUIRED_CONFIDENCE) { + return; + } + + long recordingTrackId = PreferencesUtils.getLong(this, R.string.recording_track_id_key); + if (recordingTrackId == PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { + return; + } + boolean recordingTrackPaused = PreferencesUtils.getBoolean( + this, R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT); + if (recordingTrackPaused) { + return; + } + + int currentType = PreferencesUtils.getInt(this, R.string.activity_recognition_type_key, + PreferencesUtils.ACTIVITY_RECOGNITION_TYPE_DEFAULT); + + switch (detectedActivity.getType()) { + case DetectedActivity.IN_VEHICLE: + if (currentType != DetectedActivity.IN_VEHICLE) { + PreferencesUtils.setInt( + this, R.string.activity_recognition_type_key, DetectedActivity.IN_VEHICLE); + } + break; + case DetectedActivity.ON_BICYCLE: + if (currentType != DetectedActivity.IN_VEHICLE + && currentType != DetectedActivity.ON_BICYCLE) { + PreferencesUtils.setInt( + this, R.string.activity_recognition_type_key, DetectedActivity.ON_BICYCLE); + } + break; + case DetectedActivity.ON_FOOT: + if (currentType != DetectedActivity.IN_VEHICLE && currentType != DetectedActivity.ON_BICYCLE + && currentType != DetectedActivity.ON_FOOT) { + PreferencesUtils.setInt( + this, R.string.activity_recognition_type_key, DetectedActivity.ON_FOOT); + } + break; + default: + break; + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java index ebfe0b369..a094155f9 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -41,6 +41,11 @@ import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.PreferencesUtils; import com.google.android.apps.mytracks.util.TrackIconUtils; import com.google.android.apps.mytracks.util.TrackNameUtils; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; +import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; +import com.google.android.gms.location.ActivityRecognitionClient; +import com.google.android.gms.location.DetectedActivity; import com.google.android.gms.location.LocationListener; import com.google.android.maps.mytracks.R; import com.google.common.annotations.VisibleForTesting; @@ -57,6 +62,7 @@ import android.location.Location; import android.location.LocationManager; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -81,6 +87,7 @@ import java.util.concurrent.Executors; public class TrackRecordingService extends Service { private static final String TAG = TrackRecordingService.class.getSimpleName(); + private static final long ACTIVITY_RECOGNITION_INTERVAL = 60000; // 1 minute /** * The name of extra intent property to indicate whether we want to resume a @@ -103,6 +110,8 @@ public class TrackRecordingService extends Service { private Context context; private MyTracksProviderUtils myTracksProviderUtils; private MyTracksLocationManager myTracksLocationManager; + private ActivityRecognitionClient activityRecognitionClient; + private PendingIntent activityRecognitionPendingIntent; private PeriodicTaskExecutor voiceExecutor; private PeriodicTaskExecutor splitExecutor; private ExecutorService executorService; @@ -247,6 +256,23 @@ public class TrackRecordingService extends Service { } }; + private final ConnectionCallbacks activityRecognitionCallbacks = new ConnectionCallbacks() { + @Override + public void onDisconnected() {} + + @Override + public void onConnected(Bundle bundle) { + activityRecognitionClient.requestActivityUpdates( + ACTIVITY_RECOGNITION_INTERVAL, activityRecognitionPendingIntent); + } + }; + + private final OnConnectionFailedListener + activityRecognitionFailedListener = new OnConnectionFailedListener() { + + @Override + public void onConnectionFailed(ConnectionResult connectionResult) {} + }; /* * Note that this service, through the AndroidManifest.xml, is configured to * allow both MyTracks and third party apps to invoke it. For the onCreate @@ -260,6 +286,12 @@ public class TrackRecordingService extends Service { context = this; myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this); myTracksLocationManager = new MyTracksLocationManager(this, Looper.myLooper()); + activityRecognitionPendingIntent = PendingIntent.getService(context, 0, + new Intent(context, ActivityRecognitionIntentService.class), + PendingIntent.FLAG_UPDATE_CURRENT); + activityRecognitionClient = new ActivityRecognitionClient( + context, activityRecognitionCallbacks, activityRecognitionFailedListener); + activityRecognitionClient.connect(); voiceExecutor = new PeriodicTaskExecutor(this, new AnnouncementPeriodicTaskFactory()); splitExecutor = new PeriodicTaskExecutor(this, new SplitPeriodicTaskFactory()); executorService = Executors.newSingleThreadExecutor(); @@ -352,6 +384,13 @@ public class TrackRecordingService extends Service { myTracksProviderUtils = null; myTracksLocationManager.close(); myTracksLocationManager = null; + + if (activityRecognitionClient.isConnected()) { + activityRecognitionClient.removeActivityUpdates(activityRecognitionPendingIntent); + } + activityRecognitionClient.disconnect(); + activityRecognitionPendingIntent.cancel(); + binder.detachFromService(); binder = null; @@ -568,6 +607,8 @@ public class TrackRecordingService extends Service { // Update shared preferences updateRecordingState(trackId, false); PreferencesUtils.setInt(this, R.string.auto_resume_track_current_retry_key, 0); + PreferencesUtils.setInt(this, R.string.activity_recognition_type_key, + PreferencesUtils.ACTIVITY_RECOGNITION_TYPE_DEFAULT); // Update database track.setId(trackId); @@ -711,7 +752,31 @@ public class TrackRecordingService extends Service { Track track = myTracksProviderUtils.getTrack(trackId); if (track != null && !paused) { insertLocation(track, lastLocation, getLastValidTrackPointInCurrentSegment(trackId)); - updateRecordingTrack(track, myTracksProviderUtils.getLastTrackPointId(trackId), false); + + int activityRecognitionType = PreferencesUtils.getInt(this, + R.string.activity_recognition_type_key, + PreferencesUtils.ACTIVITY_RECOGNITION_TYPE_DEFAULT); + if (activityRecognitionType != PreferencesUtils.ACTIVITY_RECOGNITION_TYPE_DEFAULT) { + String iconValue = null; + switch (activityRecognitionType) { + case DetectedActivity.IN_VEHICLE: + iconValue = TrackIconUtils.DRIVE; + break; + case DetectedActivity.ON_BICYCLE: + iconValue = TrackIconUtils.BIKE; + break; + case DetectedActivity.ON_FOOT: + iconValue = TrackIconUtils.WALK; + break; + default: + break; + } + if (iconValue != null) { + track.setIcon(iconValue); + track.setCategory(getString(TrackIconUtils.getIconActivityType(iconValue))); + } + } + updateRecordingTrack(track, myTracksProviderUtils.getLastTrackPointId(trackId), false); } endRecording(true, trackId); diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PreferencesUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PreferencesUtils.java index 0bd4fd413..b110d8286 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/PreferencesUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/PreferencesUtils.java @@ -17,6 +17,7 @@ package com.google.android.apps.mytracks.util; import com.google.android.apps.mytracks.Constants; +import com.google.android.gms.location.DetectedActivity; import android.annotation.SuppressLint; import android.content.Context; @@ -30,6 +31,8 @@ import android.content.SharedPreferences.Editor; */ public class PreferencesUtils { + public static final int ACTIVITY_RECOGNITION_TYPE_DEFAULT = DetectedActivity.UNKNOWN; + /* * Preferences values. The defaults need to match the defaults in the xml * files. diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/TrackIconUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/TrackIconUtils.java index 741680c55..d5a210d6e 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/TrackIconUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/TrackIconUtils.java @@ -35,13 +35,13 @@ import java.util.List; public class TrackIconUtils { private static final String AIRPLANE = "AIRPLANE"; - private static final String BIKE = "BIKE"; + public static final String BIKE = "BIKE"; private static final String BOAT = "BOAT"; - private static final String DRIVE = "DRIVE"; + public static final String DRIVE = "DRIVE"; private static final String RUN = "RUN"; private static final String SKI = "SKI"; private static final String SNOW_BOARDING = "SNOW_BOARDING"; - private static final String WALK = "WALK"; + public static final String WALK = "WALK"; private TrackIconUtils() {}