diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index f4dc05e85..b928d7c8e 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -178,6 +178,9 @@ limitations under the License. android:exported="true" android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" /> + + diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml index e6e472948..4424e4971 100644 --- a/MyTracks/res/values/do_not_translate.xml +++ b/MyTracks/res/values/do_not_translate.xml @@ -50,4 +50,7 @@ limitations under the License. android.intent.action.SAVE android.intent.action.settings.OPEN + com.google.android.apps.mytracks.START_NEW_TRACK + com.google.android.apps.mytracks.END_CURRENT_TRACK + diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java new file mode 100644 index 000000000..e0f70553e --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java @@ -0,0 +1,119 @@ +/* + * Copyright 2011 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 static com.google.android.apps.mytracks.Constants.TAG; + +import com.google.android.apps.mytracks.widgets.TrackWidgetProvider; +import com.google.android.maps.mytracks.R; + +import android.app.IntentService; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +/** + * A service to control starting and stopping of a recording. This service, + * through the AndroidManifest.xml, is configured to only allow components of + * the same application to invoke it. Thus this service can be used my MyTracks + * app widget, {@link TrackWidgetProvider}, but not by other applications. This + * application delegates starting and stopping a recording to + * {@link TrackRecordingService} using RPC calls. + * + * @author jshih@google.com (Jimmy Shih) + */ +public class ControlRecordingService extends IntentService implements ServiceConnection { + + private ITrackRecordingService trackRecordingService; + private boolean connected = false; + + public ControlRecordingService() { + super(ControlRecordingService.class.getSimpleName()); + } + + @Override + public void onCreate() { + super.onCreate(); + + Intent newIntent = new Intent(this, TrackRecordingService.class); + startService(newIntent); + bindService(newIntent, this, 0); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + trackRecordingService = ITrackRecordingService.Stub.asInterface(service); + notifyConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + connected = false; + } + + /** + * Notifies all threads that connection to {@link TrackRecordingService} is + * available. + */ + private synchronized void notifyConnected() { + connected = true; + notifyAll(); + } + + /** + * Waits until the connection to {@link TrackRecordingService} is available. + */ + private synchronized void waitConnected() { + while (!connected) { + try { + wait(); + } catch (InterruptedException e) { + + // can safely ignore + } + } + } + + @Override + protected void onHandleIntent(Intent intent) { + waitConnected(); + String action = intent.getAction(); + if (action != null) { + try { + if (action.equals(getString(R.string.start_new_track_action))) { + trackRecordingService.startNewTrack(); + } else if (action.equals(getString(R.string.end_current_track_action))) { + trackRecordingService.endCurrentTrack(); + } + } catch (RemoteException e) { + Log.d(TAG, "ControlRecordingService onHandleIntent RemoteException", e); + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (connected) { + unbindService(this); + connected = false; + } + } +} 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 f89f65e45..ac0f9e08c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -56,11 +56,13 @@ import android.location.Location; import android.location.LocationListener; 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.PowerManager; import android.os.PowerManager.WakeLock; +import android.os.Process; import android.util.Log; import java.util.Timer; @@ -278,39 +280,24 @@ public class TrackRecordingService extends Service { handleStartCommand(intent, startId); return START_STICKY; } - + + /** + * Handles onStart and onStartCommand. This service, through the + * AndroidManifest.xml, is configured to allow both MyTracks and other + * applications to invoke it. With only the intent, we cannot tell whether the + * caller is MyTracks or another app. Thus when starting the service through + * this method, we cannot read/write MyTracks data or start/stop a recording. + */ private void handleStartCommand(Intent intent, int startId) { - Log.d(TAG, - "TrackRecordingService.handleStartCommand: " + startId); + Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId); - if (intent == null) + if (intent == null) { return; - + } + // Check if called on phone reboot with resume intent. if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) { resumeTrack(startId); - } else { - // Process actions for controlling the service. - processStartStopIntent(intent); - } - } - - private void processStartStopIntent(Intent intent) { - String action = intent.getAction(); - - if (isNewTrackAction(action)) { - if (!isTrackInProgress()) { - boolean selectNewTrack = intent.getBooleanExtra(getString(R.string.select_new_track_extra), false); - - startNewTrack(); - if (selectNewTrack) { - prefManager.setSelectedTrack(recordingTrackId); - } - } - } else if (isEndTrackAction(action)) { - if (isTrackInProgress()) { - endCurrentTrack(); - } } } @@ -318,14 +305,6 @@ public class TrackRecordingService extends Service { return recordingTrackId != -1 || isRecording; } - private boolean isNewTrackAction(String action) { - return getString(R.string.start_new_track_action).equals(action); - } - - private boolean isEndTrackAction(String action) { - return getString(R.string.end_current_track_action).equals(action); - } - private void resumeTrack(int startId) { Log.d(TAG, "TrackRecordingService: requested resume"); @@ -574,7 +553,7 @@ public class TrackRecordingService extends Service { public long startNewTrack() { Log.d(TAG, "TrackRecordingService.startNewTrack"); if (isTrackInProgress()) { - throw new IllegalStateException("A track is already in progress!"); + return -1L; } long startTime = System.currentTimeMillis(); @@ -982,8 +961,7 @@ public class TrackRecordingService extends Service { * Build a statistics marker. * A statistics marker holds the stats for the* last segment up to this marker. * - * @param Waypoint The waypoint which will be populated with stats data. - * @return the unique id of the inserted marker + * @param waypoint The waypoint which will be populated with stats data. */ private void buildStatisticsMarker(Waypoint waypoint) { StringUtils utils = new StringUtils(TrackRecordingService.this); @@ -1012,7 +990,7 @@ public class TrackRecordingService extends Service { private void endCurrentTrack() { Log.d(TAG, "TrackRecordingService.endCurrentTrack"); if (!isTrackInProgress()) { - throw new IllegalStateException("No recording track in progress!"); + return; } announcementExecutor.shutdown(); @@ -1195,24 +1173,48 @@ public class TrackRecordingService extends Service { throw new IllegalStateException("The service has been already detached!"); } } + + /** + * Returns true if the RPC caller is from the same application or if the + * sharing setting indicates that another app can invoke this service's + * RPCs. + */ + private boolean canAccess() { + if (Process.myPid() == Binder.getCallingPid()) { + return true; + } else { + SharedPreferences sharedPreferences = service.getSharedPreferences( + Constants.SETTINGS_NAME, 0); + return sharedPreferences.getBoolean(service.getString(R.string.share_data_key), false); + } + } // Service method delegates. @Override public boolean isRecording() { checkService(); + if (!canAccess()) { + return false; + } return service.isRecording(); } @Override public long getRecordingTrackId() { checkService(); + if (!canAccess()) { + return -1L; + } return service.recordingTrackId; } @Override public long startNewTrack() { checkService(); + if (!canAccess()) { + return -1L; + } return service.startNewTrack(); } @@ -1224,24 +1226,36 @@ public class TrackRecordingService extends Service { */ public long insertWaypoint(WaypointCreationRequest request) { checkService(); + if (!canAccess()) { + return -1L; + } return service.insertWaypoint(request); } @Override public void endCurrentTrack() { checkService(); + if (!canAccess()) { + return; + } service.endCurrentTrack(); } @Override public void recordLocation(Location loc) { checkService(); + if (!canAccess()) { + return; + } service.locationListener.onLocationChanged(loc); } @Override public byte[] getSensorData() { checkService(); + if (!canAccess()) { + return null; + } if (service.sensorManager == null) { Log.d(TAG, "No sensor manager for data."); return null; @@ -1256,6 +1270,9 @@ public class TrackRecordingService extends Service { @Override public int getSensorState() { checkService(); + if (!canAccess()) { + return Sensor.SensorState.NONE.getNumber(); + } if (service.sensorManager == null) { Log.d(TAG, "No sensor manager for data."); return Sensor.SensorState.NONE.getNumber(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java index 5ee51fdc0..e62a05a26 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java +++ b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java @@ -19,6 +19,16 @@ package com.google.android.apps.mytracks.widgets; import static com.google.android.apps.mytracks.Constants.SETTINGS_NAME; import static com.google.android.apps.mytracks.Constants.TAG; +import com.google.android.apps.mytracks.MyTracks; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.content.Track; +import com.google.android.apps.mytracks.content.TracksColumns; +import com.google.android.apps.mytracks.services.ControlRecordingService; +import com.google.android.apps.mytracks.stats.TripStatistics; +import com.google.android.apps.mytracks.util.StringUtils; +import com.google.android.apps.mytracks.util.UnitConversions; +import com.google.android.maps.mytracks.R; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -32,16 +42,6 @@ import android.os.Handler; import android.util.Log; import android.widget.RemoteViews; -import com.google.android.apps.mytracks.MyTracks; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.Track; -import com.google.android.apps.mytracks.content.TracksColumns; -import com.google.android.apps.mytracks.services.TrackRecordingService; -import com.google.android.apps.mytracks.stats.TripStatistics; -import com.google.android.apps.mytracks.util.StringUtils; -import com.google.android.apps.mytracks.util.UnitConversions; -import com.google.android.maps.mytracks.R; - /** * An AppWidgetProvider for displaying key track statistics (distance, time, * speed) from the current or most recent track. @@ -175,14 +175,12 @@ public class TrackWidgetProvider // If a new track is started by this appwidget or elsewhere, // toggle the button to active and have it disable the track if pressed. setButtonIntent( - views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled, - -1); + views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled); } else { // If a track is stopped by this appwidget or elsewhere, // toggle the button to inactive and have it start a new track if pressed. setButtonIntent( - views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled, - R.string.select_new_track_extra); + views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled); } } @@ -193,15 +191,11 @@ public class TrackWidgetProvider * @param context The widget context * @param action The resource id of the action to fire when the button is pressed * @param icon The resource id of the icon to show for the button - * @param extra Optional resource id of a boolean extra on the intent */ private void setButtonIntent( - RemoteViews views, Context context, int action, int icon, int extra) { - Intent intent = new Intent(context, TrackRecordingService.class); + RemoteViews views, Context context, int action, int icon) { + Intent intent = new Intent(context, ControlRecordingService.class); intent.setAction(context.getString(action)); - if (extra != -1) { - intent.putExtra(context.getString(extra), true); - } PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent); diff --git a/MyTracksLib/res/values/third_party.xml b/MyTracksLib/res/values/third_party.xml index 00fc3dd15..e85f8ff1b 100644 --- a/MyTracksLib/res/values/third_party.xml +++ b/MyTracksLib/res/values/third_party.xml @@ -28,15 +28,6 @@ limitations under the License. com.google.android.apps.mytracks.TRACK_STOPPED com.google.android.apps.mytracks.TRACK_ID - - com.google.android.apps.mytracks.START_NEW_TRACK - - com.google.android.apps.mytracks.SELECT_NEW_TRACK - com.google.android.apps.mytracks.END_CURRENT_TRACK - com.google.android.apps.mytracks.services.TrackRecordingService com.google.android.maps.mytracks