merge heads

This commit is contained in:
Jimmy Shih
2011-10-10 12:13:01 -07:00
17 changed files with 335 additions and 137 deletions
+12 -3
View File
@@ -178,6 +178,9 @@ limitations under the License.
android:exported="true" android:exported="true"
android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" /> 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"> <receiver android:name="com.google.android.apps.mytracks.BootReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -189,7 +192,6 @@ limitations under the License.
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.google.android.apps.mytracks.TRACK_STARTED" /> <action android:name="com.google.android.apps.mytracks.TRACK_STARTED" />
<action android:name="com.google.android.apps.mytracks.TRACK_STOPPED" /> <action android:name="com.google.android.apps.mytracks.TRACK_STOPPED" />
<action android:name="com.google.android.apps.mytracks.TRACK_UPDATED" />
</intent-filter> </intent-filter>
<meta-data android:name="android.appwidget.provider" <meta-data android:name="android.appwidget.provider"
android:resource="@xml/track_widget_info" /> android:resource="@xml/track_widget_info" />
@@ -231,7 +233,7 @@ limitations under the License.
android:permissionGroup="android.permission-group.LOCATION" android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/notification_permission_label" android:label="@string/notification_permission_label"
android:description="@string/notification_permission_description" android:description="@string/notification_permission_description"
android:protectionLevel="normal" /> android:protectionLevel="dangerous" />
<!-- Permission to read raw track data (points, etc.) --> <!-- Permission to read raw track data (points, etc.) -->
<permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA" <permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA"
android:permissionGroup="android.permission-group.LOCATION" android:permissionGroup="android.permission-group.LOCATION"
@@ -245,10 +247,17 @@ limitations under the License.
android:description="@string/write_tracks_permission_description" android:description="@string/write_tracks_permission_description"
android:protectionLevel="dangerous" /> android:protectionLevel="dangerous" />
<!-- Permission to get MyTracks broadcasts. For internal MyTracks communication. -->
<permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS"
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/mytracks_notifications_permission_label"
android:description="@string/mytracks_notifications_permission_description"
android:protectionLevel="signature" />
<!-- We read and write raw track data, obviously. --> <!-- We read and write raw track data, obviously. -->
<uses-permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA" /> <uses-permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.WRITE_TRACK_DATA" /> <uses-permission android:name="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.TRACK_NOTIFICATIONS" /> <uses-permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS" />
<!-- Features required --> <!-- Features required -->
<uses-feature android:name="android.hardware.location" android:required="true"/> <uses-feature android:name="android.hardware.location" android:required="true"/>
+7
View File
@@ -50,4 +50,11 @@ limitations under the License.
<!-- Intent actions --> <!-- Intent actions -->
<string name="save_intent_action">android.intent.action.SAVE</string> <string name="save_intent_action">android.intent.action.SAVE</string>
<string name="open_settings_screen">android.intent.action.settings.OPEN</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>
<!-- Permissions -->
<!-- Permission for MyTracks notifications. -->
<string name="mytracks_notifications_permission">com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS</string>
</resources> </resources>
+19 -14
View File
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<!-- Keys for application preferences - DO NOT TRANSLATE --> <!-- Keys for application preferences. Sorted alphabetically. - DO NOT TRANSLATE -->
<string name="allow_access_key">allowAccess</string>
<string name="announcement_frequency_key">announcementFrequency</string> <string name="announcement_frequency_key">announcementFrequency</string>
<string name="ant_heart_rate_sensor_id_key">antHeartRateSensorId</string> <string name="ant_heart_rate_sensor_id_key">antHeartRateSensorId</string>
<string name="ant_power_sensor_id_key">antPowerSensorId</string> <string name="ant_power_sensor_id_key">antPowerSensorId</string>
@@ -35,30 +36,34 @@ limitations under the License.
<string name="report_speed_key">reportSpeed</string> <string name="report_speed_key">reportSpeed</string>
<string name="send_stats_and_points_key">sendStatsAndPoints</string> <string name="send_stats_and_points_key">sendStatsAndPoints</string>
<string name="send_to_docs_key">sendToDocs</string> <string name="send_to_docs_key">sendToDocs</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="send_to_fusion_tables_key">sendToFusionTables</string> <string name="send_to_fusion_tables_key">sendToFusionTables</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="sensor_type_key">sensorType</string> <string name="sensor_type_key">sensorType</string>
<string name="share_url_only_key">shareUrlOnly</string> <string name="share_url_only_key">shareUrlOnly</string>
<string name="split_frequency_key">splitFrequency</string> <string name="split_frequency_key">splitFrequency</string>
<string name="timestamp_track_name_key">timestampTrackName</string> <string name="timestamp_track_name_key">timestampTrackName</string>
<string name="track_color_mode_key">trackColorMode</string> <string name="track_color_mode_dynamic_speed_variation_key">trackColorModeDynamicVariation
<string name="track_color_mode_fixed_speed_slow_key">trackColorModeFixedSlow</string> </string>
<string name="track_color_mode_fixed_speed_medium_display_key">trackColorModeFixedMediumDisplay
</string>
<string name="track_color_mode_fixed_speed_medium_key">trackColorModeFixedMedium</string> <string name="track_color_mode_fixed_speed_medium_key">trackColorModeFixedMedium</string>
<string name="track_color_mode_fixed_speed_slow_display_key">trackColorModeFixedSlowDisplay</string> <string name="track_color_mode_fixed_speed_slow_display_key">trackColorModeFixedSlowDisplay
<string name="track_color_mode_fixed_speed_medium_display_key">trackColorModeFixedMediumDisplay</string> </string>
<string name="track_color_mode_dynamic_speed_variation_key">trackColorModeDynamicVariation</string> <string name="track_color_mode_fixed_speed_slow_key">trackColorModeFixedSlow</string>
<string name="track_color_mode_key">trackColorMode</string>
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
<!-- Preference keys which keep transient state (not user preferences). Sorted alphabetically.
- DO NOT TRANSLATE -->
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string> <string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
<string name="recording_track_key">recordingTrack</string> <string name="recording_track_key">recordingTrack</string>
<string name="selected_track_key">selectedTrack</string> <string name="selected_track_key">selectedTrack</string>
<!-- Keys for non-persisted preferences - DO NOT TRANSLATE --> <!-- Keys for non-persisted preferences. Sorted alphabetically. - DO NOT TRANSLATE -->
<string name="backup_to_sd_key">backupToSd</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<string name="reset_key">resetToDefaults</string>
<string name="sensor_options_key">sensorOptions</string>
<string name="ant_options_key">antOptions</string> <string name="ant_options_key">antOptions</string>
<string name="backup_to_sd_key">backupToSd</string>
<string name="display_settings_screen_key">displaySettingsScreen</string> <string name="display_settings_screen_key">displaySettingsScreen</string>
<string name="reset_key">resetToDefaults</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<string name="sensor_options_key">sensorOptions</string>
<string name="track_color_mode_fixed_speed_options_key">trackColorModeFixedOptions</string> <string name="track_color_mode_fixed_speed_options_key">trackColorModeFixedOptions</string>
</resources> </resources>
+12
View File
@@ -208,6 +208,15 @@ limitations under the License.
<string name="settings_not_available_summary">Not available in your version of Android</string> <string name="settings_not_available_summary">Not available in your version of Android</string>
<string name="settings_not_while_recording">Not available while recording a track</string> <string name="settings_not_while_recording">Not available while recording a track</string>
<string name="settings_allow_access">Allow access</string>
<string name="settings_allow_access_summary_on">Other apps can read, write, start/stop recording
MyTracks data.</string>
<string name="settings_allow_access_summary_off">Other apps cannot read, write, start/stop
recording MyTracks data.</string>
<string name="settings_allow_access_dialog_message">Are you sure you want to allow other apps,
including future apps, to control and access MyTracks data? There will be no further
notification.</string>
<string name="settings_announcement_frequency">Announcement frequency</string> <string name="settings_announcement_frequency">Announcement frequency</string>
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string> <string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string>
@@ -518,11 +527,14 @@ limitations under the License.
<!-- Manifest description (not seen inside the app) --> <!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">receive MyTracks notifications</string> <string name="notification_permission_label">receive MyTracks notifications</string>
<string name="mytracks_notifications_permission_label">receive MyTracks notifications</string>
<string name="read_tracks_permission_label">read MyTracks data</string> <string name="read_tracks_permission_label">read MyTracks data</string>
<string name="write_tracks_permission_label">write data to MyTracks and start/stop MyTracks <string name="write_tracks_permission_label">write data to MyTracks and start/stop MyTracks
recording</string> recording</string>
<string name="notification_permission_description">Allows the application to receive notifications <string name="notification_permission_description">Allows the application to receive notifications
when MyTracks starts or stops a recording.</string> when MyTracks starts or stops a recording.</string>
<string name="mytracks_notifications_permission_description">receive MyTracks notifications
</string>
<string name="read_tracks_permission_description">Allows the application to read MyTracks data. <string name="read_tracks_permission_description">Allows the application to read MyTracks data.
</string> </string>
<string name="write_tracks_permission_description">Allows the application to write data to <string name="write_tracks_permission_description">Allows the application to write data to
+7
View File
@@ -209,6 +209,13 @@ limitations under the License.
android:summaryOn="@string/settings_share_url_only_summary_on" android:summaryOn="@string/settings_share_url_only_summary_on"
android:summaryOff="@string/settings_share_url_only_summary_off" android:summaryOff="@string/settings_share_url_only_summary_off"
android:defaultValue="false" /> android:defaultValue="false" />
<CheckBoxPreference
android:key="@string/allow_access_key"
android:title="@string/settings_allow_access"
android:summaryOn="@string/settings_allow_access_summary_on"
android:summaryOff="@string/settings_allow_access_summary_off"
android:defaultValue="false" />
</PreferenceScreen> </PreferenceScreen>
<!-- Backup settings --> <!-- Backup settings -->
@@ -142,6 +142,33 @@ public class SettingsActivity extends PreferenceActivity {
return true; return true;
} }
}); });
// Add a confirmation dialog for the "Allow access" preference.
final CheckBoxPreference allowAccessPreference = (CheckBoxPreference) findPreference(
getString(R.string.allow_access_key));
allowAccessPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean) newValue) {
AlertDialog dialog = new AlertDialog.Builder(SettingsActivity.this)
.setCancelable(true)
.setTitle(getString(R.string.settings_allow_access))
.setMessage(getString(R.string.settings_allow_access_dialog_message))
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int button) {
allowAccessPreference.setChecked(true);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.show();
return false;
} else {
return true;
}
}
});
} }
private boolean processIntent() { private boolean processIntent() {
@@ -17,11 +17,13 @@ package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.ApiFeatures; import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.UriMatcher; import android.content.UriMatcher;
import android.database.Cursor; import android.database.Cursor;
import android.database.SQLException; import android.database.SQLException;
@@ -30,6 +32,8 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -183,8 +187,22 @@ public class MyTracksProvider extends ContentProvider {
"waypoints/#", WAYPOINTS_ID); "waypoints/#", WAYPOINTS_ID);
} }
private boolean canAccess() {
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
Context context = getContext();
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(context.getString(R.string.allow_access_key), false);
}
}
@Override @Override
public boolean onCreate() { public boolean onCreate() {
if (!canAccess()) {
return false;
}
DatabaseHelper dbHelper = new DatabaseHelper(getContext()); DatabaseHelper dbHelper = new DatabaseHelper(getContext());
try { try {
db = dbHelper.getWritableDatabase(); db = dbHelper.getWritableDatabase();
@@ -196,6 +214,9 @@ public class MyTracksProvider extends ContentProvider {
@Override @Override
public int delete(Uri url, String where, String[] selectionArgs) { public int delete(Uri url, String where, String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
String table; String table;
boolean shouldVacuum = false; boolean shouldVacuum = false;
switch (urlMatcher.match(url)) { switch (urlMatcher.match(url)) {
@@ -228,6 +249,9 @@ public class MyTracksProvider extends ContentProvider {
@Override @Override
public String getType(Uri url) { public String getType(Uri url) {
if (!canAccess()) {
return null;
}
switch (urlMatcher.match(url)) { switch (urlMatcher.match(url)) {
case TRACKPOINTS: case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE; return TrackPointsColumns.CONTENT_TYPE;
@@ -248,6 +272,9 @@ public class MyTracksProvider extends ContentProvider {
@Override @Override
public Uri insert(Uri url, ContentValues initialValues) { public Uri insert(Uri url, ContentValues initialValues) {
if (!canAccess()) {
return null;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert"); Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert");
ContentValues values; ContentValues values;
if (initialValues != null) { if (initialValues != null) {
@@ -276,6 +303,9 @@ public class MyTracksProvider extends ContentProvider {
@Override @Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) { public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
if (!canAccess()) {
return 0;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert"); Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0; int numInserted = 0;
try { try {
@@ -348,6 +378,9 @@ public class MyTracksProvider extends ContentProvider {
public Cursor query( public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs, Uri url, String[] projection, String selection, String[] selectionArgs,
String sort) { String sort) {
if (!canAccess()) {
return null;
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = urlMatcher.match(url); int match = urlMatcher.match(url);
String sortOrder = null; String sortOrder = null;
@@ -399,6 +432,9 @@ public class MyTracksProvider extends ContentProvider {
@Override @Override
public int update(Uri url, ContentValues values, String where, public int update(Uri url, ContentValues values, String where,
String[] selectionArgs) { String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
int count; int count;
int match = urlMatcher.match(url); int match = urlMatcher.match(url);
if (match == TRACKPOINTS) { if (match == TRACKPOINTS) {
@@ -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.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.os.Binder;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerManager.WakeLock; import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.util.Log; import android.util.Log;
import java.util.Timer; import java.util.Timer;
@@ -278,39 +280,24 @@ public class TrackRecordingService extends Service {
handleStartCommand(intent, startId); handleStartCommand(intent, startId);
return START_STICKY; 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) { private void handleStartCommand(Intent intent, int startId) {
Log.d(TAG, Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId);
"TrackRecordingService.handleStartCommand: " + startId);
if (intent == null) if (intent == null) {
return; return;
}
// Check if called on phone reboot with resume intent. // Check if called on phone reboot with resume intent.
if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) { if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
resumeTrack(startId); 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; 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) { private void resumeTrack(int startId) {
Log.d(TAG, "TrackRecordingService: requested resume"); Log.d(TAG, "TrackRecordingService: requested resume");
@@ -574,7 +553,7 @@ public class TrackRecordingService extends Service {
public long startNewTrack() { public long startNewTrack() {
Log.d(TAG, "TrackRecordingService.startNewTrack"); Log.d(TAG, "TrackRecordingService.startNewTrack");
if (isTrackInProgress()) { if (isTrackInProgress()) {
throw new IllegalStateException("A track is already in progress!"); return -1L;
} }
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@@ -982,8 +961,7 @@ public class TrackRecordingService extends Service {
* Build a statistics marker. * Build a statistics marker.
* A statistics marker holds the stats for the* last segment up to this 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. * @param waypoint The waypoint which will be populated with stats data.
* @return the unique id of the inserted marker
*/ */
private void buildStatisticsMarker(Waypoint waypoint) { private void buildStatisticsMarker(Waypoint waypoint) {
StringUtils utils = new StringUtils(TrackRecordingService.this); StringUtils utils = new StringUtils(TrackRecordingService.this);
@@ -1012,7 +990,7 @@ public class TrackRecordingService extends Service {
private void endCurrentTrack() { private void endCurrentTrack() {
Log.d(TAG, "TrackRecordingService.endCurrentTrack"); Log.d(TAG, "TrackRecordingService.endCurrentTrack");
if (!isTrackInProgress()) { if (!isTrackInProgress()) {
throw new IllegalStateException("No recording track in progress!"); return;
} }
announcementExecutor.shutdown(); announcementExecutor.shutdown();
@@ -1053,12 +1031,14 @@ public class TrackRecordingService extends Service {
} }
private void sendTrackBroadcast(int actionResId, long trackId) { private void sendTrackBroadcast(int actionResId, long trackId) {
Intent broadcastIntent = Intent broadcastIntent = new Intent().setAction(getString(actionResId)).putExtra(
new Intent() getString(R.string.track_id_broadcast_extra), trackId);
.setAction(getString(actionResId)) sendBroadcast(broadcastIntent, getString(R.string.mytracks_notifications_permission));
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent, SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
getString(R.string.broadcast_notifications_permission)); if (sharedPreferences.getBoolean(getString(R.string.allow_access_key), false)) {
sendBroadcast(broadcastIntent, getString(R.string.broadcast_notifications_permission));
}
} }
/* /*
@@ -1190,29 +1170,58 @@ public class TrackRecordingService extends Service {
} }
} }
/**
* Checks if the service is available. If not, throws an
* {@link IllegalStateException}.
*/
private void checkService() { private void checkService() {
if (service == null) { if (service == null) {
throw new IllegalStateException("The service has been already detached!"); throw new IllegalStateException("The service has been already detached!");
} }
} }
/**
* Returns true if the RPC caller is from the same application or if the
* "Allow access" setting indicates that another app can invoke this service's
* RPCs.
*/
private boolean canAccess() {
// As a precondition for access, must check if the service is available.
checkService();
if (Process.myPid() == Binder.getCallingPid()) {
return true;
} else {
SharedPreferences sharedPreferences = service.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(service.getString(R.string.allow_access_key), false);
}
}
// Service method delegates. // Service method delegates.
@Override @Override
public boolean isRecording() { public boolean isRecording() {
checkService(); if (!canAccess()) {
return false;
}
return service.isRecording(); return service.isRecording();
} }
@Override @Override
public long getRecordingTrackId() { public long getRecordingTrackId() {
checkService(); if (!canAccess()) {
return -1L;
}
return service.recordingTrackId; return service.recordingTrackId;
} }
@Override @Override
public long startNewTrack() { public long startNewTrack() {
checkService(); if (!canAccess()) {
return -1L;
}
return service.startNewTrack(); return service.startNewTrack();
} }
@@ -1223,25 +1232,33 @@ public class TrackRecordingService extends Service {
* @return the unique ID of the inserted marker * @return the unique ID of the inserted marker
*/ */
public long insertWaypoint(WaypointCreationRequest request) { public long insertWaypoint(WaypointCreationRequest request) {
checkService(); if (!canAccess()) {
return -1L;
}
return service.insertWaypoint(request); return service.insertWaypoint(request);
} }
@Override @Override
public void endCurrentTrack() { public void endCurrentTrack() {
checkService(); if (!canAccess()) {
return;
}
service.endCurrentTrack(); service.endCurrentTrack();
} }
@Override @Override
public void recordLocation(Location loc) { public void recordLocation(Location loc) {
checkService(); if (!canAccess()) {
return;
}
service.locationListener.onLocationChanged(loc); service.locationListener.onLocationChanged(loc);
} }
@Override @Override
public byte[] getSensorData() { public byte[] getSensorData() {
checkService(); if (!canAccess()) {
return null;
}
if (service.sensorManager == null) { if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data."); Log.d(TAG, "No sensor manager for data.");
return null; return null;
@@ -1255,7 +1272,9 @@ public class TrackRecordingService extends Service {
@Override @Override
public int getSensorState() { public int getSensorState() {
checkService(); if (!canAccess()) {
return Sensor.SensorState.NONE.getNumber();
}
if (service.sensorManager == null) { if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data."); Log.d(TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber(); 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.SETTINGS_NAME;
import static com.google.android.apps.mytracks.Constants.TAG; 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.app.PendingIntent;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider; import android.appwidget.AppWidgetProvider;
@@ -32,16 +42,6 @@ import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.widget.RemoteViews; 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, * An AppWidgetProvider for displaying key track statistics (distance, time,
* speed) from the current or most recent track. * 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, // If a new track is started by this appwidget or elsewhere,
// toggle the button to active and have it disable the track if pressed. // toggle the button to active and have it disable the track if pressed.
setButtonIntent( setButtonIntent(
views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled, views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled);
-1);
} else { } else {
// If a track is stopped by this appwidget or elsewhere, // If a track is stopped by this appwidget or elsewhere,
// toggle the button to inactive and have it start a new track if pressed. // toggle the button to inactive and have it start a new track if pressed.
setButtonIntent( setButtonIntent(
views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled, views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled);
R.string.select_new_track_extra);
} }
} }
@@ -193,15 +191,11 @@ public class TrackWidgetProvider
* @param context The widget context * @param context The widget context
* @param action The resource id of the action to fire when the button is pressed * @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 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( private void setButtonIntent(
RemoteViews views, Context context, int action, int icon, int extra) { RemoteViews views, Context context, int action, int icon) {
Intent intent = new Intent(context, TrackRecordingService.class); Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(context.getString(action)); intent.setAction(context.getString(action));
if (extra != -1) {
intent.putExtra(context.getString(extra), true);
}
PendingIntent pendingIntent = PendingIntent.getService(context, 0, PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT); intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent); views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
-11
View File
@@ -23,20 +23,9 @@ limitations under the License.
<!-- Broadcast notification Intent constants --> <!-- Broadcast notification Intent constants -->
<string name="track_started_broadcast_action">com.google.android.apps.mytracks.TRACK_STARTED</string> <string name="track_started_broadcast_action">com.google.android.apps.mytracks.TRACK_STARTED</string>
<string name="track_paused_broadcast_action">com.google.android.apps.mytracks.TRACK_PAUSED</string>
<string name="track_resumed_broadcast_action">com.google.android.apps.mytracks.TRACK_RESUMED</string>
<string name="track_stopped_broadcast_action">com.google.android.apps.mytracks.TRACK_STOPPED</string> <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> <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 --> <!-- My Tracks service constants -->
<string name="mytracks_service_class">com.google.android.apps.mytracks.services.TrackRecordingService</string> <string name="mytracks_service_class">com.google.android.apps.mytracks.services.TrackRecordingService</string>
<string name="mytracks_service_package">com.google.android.maps.mytracks</string> <string name="mytracks_service_package">com.google.android.maps.mytracks</string>
@@ -63,7 +63,6 @@ public class MapOverlayTest extends AndroidTestCase {
assertEquals(2, myTracksOverlay.getNumLocations()); assertEquals(2, myTracksOverlay.getNumLocations());
assertEquals(0, myTracksOverlay.getNumWaypoints()); assertEquals(0, myTracksOverlay.getNumWaypoints());
assertNotNull(myTracksOverlay.getLastPath()); assertNotNull(myTracksOverlay.getLastPath());
assertEquals(2, ((MockPath)myTracksOverlay.getLastPath()).totalPoints);
myTracksOverlay.draw(canvas, mockView, true); myTracksOverlay.draw(canvas, mockView, true);
assertEquals(2, myTracksOverlay.getNumLocations()); assertEquals(2, myTracksOverlay.getNumLocations());
@@ -152,11 +151,8 @@ public class MapOverlayTest extends AndroidTestCase {
// No shadow. // No shadow.
myTracksOverlay.draw(canvas, mockView, false); myTracksOverlay.draw(canvas, mockView, false);
assertNotNull(myTracksOverlay.getLastPath()); assertNotNull(myTracksOverlay.getLastPath());
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
MockPath path = (MockPath) myTracksOverlay.getLastPath();
assertEquals(40, myTracksOverlay.getNumWaypoints()); assertEquals(40, myTracksOverlay.getNumWaypoints());
assertEquals(100, myTracksOverlay.getNumLocations()); assertEquals(100, myTracksOverlay.getNumLocations());
assertEquals(100, path.totalPoints);
// TODO: Check the points from the path (and the segments). // TODO: Check the points from the path (and the segments).
} }
} }
@@ -15,8 +15,6 @@
*/ */
package com.google.android.apps.mytracks.maps; package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.MockPath;
import android.location.Location; import android.location.Location;
/** /**
@@ -45,9 +43,6 @@ public class TrackPathDescriptorDynamicSpeedTest extends TrackPathPainterTestCas
painter.updatePath(myTracksOverlay.getMapProjection(mockView), painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible, myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints()); myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getLastPath()); painter.drawTrack(canvas);
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
painter.drawTrack(canvas);
} }
} }
@@ -15,8 +15,6 @@
*/ */
package com.google.android.apps.mytracks.maps; package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.MockPath;
import android.location.Location; import android.location.Location;
/** /**
@@ -45,8 +43,6 @@ public class TrackPathDescriptorFixedSpeedTest extends TrackPathPainterTestCase
painter.updatePath(myTracksOverlay.getMapProjection(mockView), painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible, myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints()); myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getLastPath());
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
painter.drawTrack(canvas); painter.drawTrack(canvas);
} }
} }
@@ -16,7 +16,6 @@
package com.google.android.apps.mytracks.maps; package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MockPath;
import com.google.android.maps.mytracks.R; import com.google.android.maps.mytracks.R;
import android.content.Context; import android.content.Context;
@@ -57,8 +56,8 @@ public class TrackPathPainterFactoryTest extends TrackPathPainterTestCase {
private <T> void testTrackPathPainterFactorySpecific(Context context, SharedPreferences prefs, private <T> void testTrackPathPainterFactorySpecific(Context context, SharedPreferences prefs,
int track_color_mode, Class <?> c) { int track_color_mode, Class <?> c) {
prefs.edit().putString(context.getString(track_color_mode), prefs.edit().putString(context.getString(R.string.track_color_mode_key),
context.getString(R.string.track_color_mode_key)).commit(); context.getString(track_color_mode)).commit();
int startLocationIdx = 0; int startLocationIdx = 0;
Boolean alwaysVisible = true; Boolean alwaysVisible = true;
@@ -71,8 +70,6 @@ public class TrackPathPainterFactoryTest extends TrackPathPainterTestCase {
painter.updatePath(myTracksOverlay.getMapProjection(mockView), painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible, myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints()); myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getLastPath());
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
painter.drawTrack(canvas); painter.drawTrack(canvas);
} }
} }
@@ -15,8 +15,6 @@
*/ */
package com.google.android.apps.mytracks.maps; package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.MockPath;
import android.location.Location; import android.location.Location;
/** /**
@@ -45,8 +43,6 @@ public class TrackPathPainterSingleColorTest extends TrackPathPainterTestCase {
painter.updatePath(myTracksOverlay.getMapProjection(mockView), painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible, myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints()); myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getLastPath());
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
painter.drawTrack(canvas); painter.drawTrack(canvas);
} }
} }
@@ -405,12 +405,10 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent()); ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording()); assertTrue(service.isRecording());
try { // Starting a new track when there is a recording should just return -1L.
service.startNewTrack(); long newTrack = service.startNewTrack();
fail("Expecting IllegalStateException"); assertEquals(-1L, newTrack);
} catch (IllegalStateException e) {
// Expected.
}
assertEquals(123, sharedPreferences.getLong( assertEquals(123, sharedPreferences.getLong(
context.getString(R.string.recording_track_key), 0)); context.getString(R.string.recording_track_key), 0));
assertEquals(123, service.getRecordingTrackId()); assertEquals(123, service.getRecordingTrackId());
@@ -452,13 +450,9 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent()); ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording()); assertFalse(service.isRecording());
// End the current track. // Ending the current track when there is no recording should not result in any error.
try { service.endCurrentTrack();
service.endCurrentTrack();
fail("Expecting IllegalStateException");
} catch (IllegalStateException e) {
// Expected.
}
assertEquals(-1, sharedPreferences.getLong( assertEquals(-1, sharedPreferences.getLong(
context.getString(R.string.recording_track_key), 0)); context.getString(R.string.recording_track_key), 0));
assertEquals(-1, service.getRecordingTrackId()); assertEquals(-1, service.getRecordingTrackId());