Protect TrackRecordingService to allow

external applications access based on the
sharing settings.

Protect all RPCs calls using the caller's
process id.

When the service is started, we cannot
tell whether the caller is MyTracks or
another app. Thus when the service
is started, we cannot access MyTracks
database or start/stop a recording.

Previsouly, only the MyTracks app
widget is using the service's start method
to start and stop a recording. Thus
create a private service, ControlRecordingService,
that only the MyTracks app widget can access and delegate
starting/stopping a recording to
TrackRecordingService.
This commit is contained in:
Jimmy Shih
2011-10-05 16:45:32 -07:00
parent 969a677d7d
commit 2fa325dbf3
6 changed files with 195 additions and 68 deletions
+3
View File
@@ -178,6 +178,9 @@ limitations under the License.
android:exported="true"
android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<service android:name="com.google.android.apps.mytracks.services.ControlRecordingService"
android:exported="false" />
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
+3
View File
@@ -50,4 +50,7 @@ limitations under the License.
<!-- Intent actions -->
<string name="save_intent_action">android.intent.action.SAVE</string>
<string name="open_settings_screen">android.intent.action.settings.OPEN</string>
<string name="start_new_track_action">com.google.android.apps.mytracks.START_NEW_TRACK</string>
<string name="end_current_track_action">com.google.android.apps.mytracks.END_CURRENT_TRACK
</string>
</resources>
@@ -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;
}
}
}
@@ -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();
@@ -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);
-9
View File
@@ -28,15 +28,6 @@ limitations under the License.
<string name="track_stopped_broadcast_action">com.google.android.apps.mytracks.TRACK_STOPPED</string>
<string name="track_id_broadcast_extra">com.google.android.apps.mytracks.TRACK_ID</string>
<!-- TrackRecordingService actions -->
<string name="start_new_track_action">com.google.android.apps.mytracks.START_NEW_TRACK</string>
<!--
Setting this intent makes the new track the selected track.
This is not expected to work if the my tracks activity is currently active.
-->
<string name="select_new_track_extra">com.google.android.apps.mytracks.SELECT_NEW_TRACK</string>
<string name="end_current_track_action">com.google.android.apps.mytracks.END_CURRENT_TRACK</string>
<!-- My Tracks service constants -->
<string name="mytracks_service_class">com.google.android.apps.mytracks.services.TrackRecordingService</string>
<string name="mytracks_service_package">com.google.android.maps.mytracks</string>