diff --git a/MyTracks/.classpath b/MyTracks/.classpath index 636d59be6..559438787 100644 --- a/MyTracks/.classpath +++ b/MyTracks/.classpath @@ -5,6 +5,7 @@ + diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 19385f398..e1d16cff4 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -17,8 +17,8 @@ limitations under the License. + android:versionCode="29" + android:versionName="1.1.6"> @@ -180,6 +180,7 @@ limitations under the License. + diff --git a/MyTracks/libs/libGoogleAnalytics.jar b/MyTracks/libs/libGoogleAnalytics.jar new file mode 100644 index 000000000..a5a4e16bf Binary files /dev/null and b/MyTracks/libs/libGoogleAnalytics.jar differ diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml index 783668362..dd6c37220 100644 --- a/MyTracks/res/values/arrays.xml +++ b/MyTracks/res/values/arrays.xml @@ -170,6 +170,11 @@ limitations under the License. inline skating skate boarding + roller skiing (classic) + roller skiing (skate) + cross-country skiing (classic) + cross-country skiing (skate) + snow shoeing skiing snow boarding diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml index c8ce04282..f6252d1db 100644 --- a/MyTracks/res/values/do_not_translate.xml +++ b/MyTracks/res/values/do_not_translate.xml @@ -39,6 +39,9 @@ limitations under the License. google.com/fusiontables maps.google.com docs.google.com + + + UA-7222692-2 android.intent.action.SAVE diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java index 2a052bbf1..1d9ba4e4c 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java @@ -145,6 +145,7 @@ public class ChartActivity extends Activity implements TrackDataListener { dataHub = TrackDataHub.getStartedInstance(); dataHub.registerTrackDataListener(this, EnumSet.of( ListenerDataType.SELECTED_TRACK_CHANGED, + ListenerDataType.TRACK_UPDATES, ListenerDataType.POINT_UPDATES, ListenerDataType.SAMPLED_OUT_POINT_UPDATES, ListenerDataType.WAYPOINT_UPDATES, diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java index 93f2e9aa5..8aa9387da 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java @@ -1,12 +1,12 @@ /* * Copyright 2009 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 @@ -133,7 +133,7 @@ public class ExportAllTracks { Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock."); } Log.i(Constants.TAG, "ExportAllTracks: Done"); - Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show(); + showToast(R.string.export_done, Toast.LENGTH_SHORT); } private void makeProgressDialog(final int trackCount) { @@ -189,12 +189,16 @@ public class ExportAllTracks { Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id); TrackWriter writer = TrackWriterFactory.newWriter(activity, providerUtils, id, format); + if (writer == null) { + showToast(R.string.error_track_does_not_exist, Toast.LENGTH_LONG); + return; + } + writer.writeTrack(); if (!writer.wasSuccess()) { // Abort the whole export on the first error. - int error = writer.getErrorMessage(); - Toast.makeText(activity, error, Toast.LENGTH_LONG).show(); + showToast(writer.getErrorMessage(), Toast.LENGTH_LONG); return; } } @@ -210,4 +214,13 @@ public class ExportAllTracks { } } } + + private void showToast(final int messageId, final int length) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(activity, messageId, length).show(); + } + }); + } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java index 5cdc369eb..d7391a211 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java @@ -53,6 +53,7 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; +import android.widget.Toast; import java.util.EnumSet; @@ -480,21 +481,21 @@ public class MapActivity extends com.google.android.maps.MapActivity @Override public void onProviderStateChange(ProviderState state) { final int messageId; - final boolean bindClick; + final boolean isGpsDisabled; switch (state) { case DISABLED: messageId = R.string.status_enable_gps; - bindClick = true; + isGpsDisabled = true; break; case NO_FIX: case BAD_FIX: messageId = R.string.wait_for_fix; - bindClick = false; + isGpsDisabled = false; break; case GOOD_FIX: // Nothing to show. messageId = -1; - bindClick = false; + isGpsDisabled = false; break; default: throw new IllegalArgumentException("Unexpected state: " + state); @@ -507,7 +508,13 @@ public class MapActivity extends com.google.android.maps.MapActivity messageText.setText(messageId); messagePane.setVisibility(View.VISIBLE); - if (bindClick) { + if (isGpsDisabled) { + // Give a warning about this state. + Toast.makeText(MapActivity.this, + R.string.error_no_gps_location_provider, + Toast.LENGTH_LONG).show(); + + // Make clicking take the user to the location settings. messagePane.setOnClickListener(MapActivity.this); } else { messagePane.setOnClickListener(null); diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index e1b1131da..44148efa0 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -17,6 +17,7 @@ package com.google.android.apps.mytracks; import static com.google.android.apps.mytracks.Constants.TAG; +import com.google.android.apps.analytics.GoogleAnalyticsTracker; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TracksColumns; @@ -79,6 +80,11 @@ public class MyTracks extends TabActivity implements OnTouchListener { */ private MyTracksProviderUtils providerUtils; + /** + * Google Analytics tracker + */ + private GoogleAnalyticsTracker tracker; + private TrackRecordingServiceBinder serviceBinder; /* @@ -125,6 +131,13 @@ public class MyTracks extends TabActivity implements OnTouchListener { apiFeatures.getApiPlatformAdapter().enableStrictMode(); } + tracker = GoogleAnalyticsTracker.getInstance(); + // Start the tracker in manual dispatch mode... + tracker.start(getString(R.string.google_analytics_id), getApplicationContext()); + tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this)); + tracker.trackPageView("/appstart"); + tracker.dispatch(); + providerUtils = MyTracksProviderUtils.Factory.get(this); preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0); dataHub = TrackDataHub.newInstance(this); @@ -179,6 +192,9 @@ public class MyTracks extends TabActivity implements OnTouchListener { dataHub.stop(); + tracker.dispatch(); + tracker.stop(); + // Clean up any temporary track files. TempFileCleaner.clean(); super.onStop(); @@ -464,10 +480,10 @@ public class MyTracks extends TabActivity implements OnTouchListener { startActivity(intent); } } - serviceBinder.stopService(); } + void clearSelectedTrack() { dataHub.unloadCurrentTrack(); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java index 1d7bed816..b31bfbefc 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java @@ -1,12 +1,12 @@ /* * Copyright 2009 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 @@ -76,15 +76,18 @@ public class WaypointsList extends ListActivity contextPosition = info.position; waypointId = WaypointsList.this.listView.getAdapter() .getItemId(contextPosition); - int type = providerUtils.getWaypoint(info.id).getType(); - menu.add(0, Constants.MENU_SHOW, 0, - R.string.waypointslist_show_waypoint); - menu.add(0, Constants.MENU_EDIT, 0, - R.string.waypointslist_edit_waypoint); - menu.add(0, Constants.MENU_DELETE, 0, - R.string.waypointslist_delete_waypoint).setEnabled( - recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT || - info.id != providerUtils.getLastWaypointId(recordingTrackId)); + Waypoint waypoint = providerUtils.getWaypoint(info.id); + if (waypoint != null) { + int type = waypoint.getType(); + menu.add(0, Constants.MENU_SHOW, 0, + R.string.waypointslist_show_waypoint); + menu.add(0, Constants.MENU_EDIT, 0, + R.string.waypointslist_edit_waypoint); + menu.add(0, Constants.MENU_DELETE, 0, + R.string.waypointslist_delete_waypoint).setEnabled( + recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT || + info.id != providerUtils.getLastWaypointId(recordingTrackId)); + } } }; @@ -162,7 +165,7 @@ public class WaypointsList extends ListActivity trackId = -1; } - final long firstWaypointId = providerUtils.getFirstWaypointId(trackId); + final long firstWaypointId = providerUtils.getFirstWaypointId(trackId); waypointsCursor = getContentResolver().query( WaypointsColumns.CONTENT_URI, null, WaypointsColumns.TRACKID + "=" + trackId + " AND " @@ -226,7 +229,7 @@ public class WaypointsList extends ListActivity new String[] { WaypointsColumns.NAME, WaypointsColumns.TIME, WaypointsColumns.CATEGORY, WaypointsColumns.TYPE }, new int[] { R.id.waypointslist_item_name, - R.id.waypointslist_item_time, + R.id.waypointslist_item_time, R.id.waypointslist_item_category, R.id.waypointslist_item_icon }); @@ -236,7 +239,7 @@ public class WaypointsList extends ListActivity waypointsCursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { @Override - public boolean setViewValue(View view, Cursor cursor, int columnIndex) { + public boolean setViewValue(View view, Cursor cursor, int columnIndex) { if (columnIndex == timeIdx) { long time = cursor.getLong(timeIdx); TextView textView = (TextView) view; diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java index 5093708bf..82e50a38f 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java @@ -41,7 +41,6 @@ import android.location.LocationManager; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; -import android.widget.Toast; import java.util.Collections; import java.util.EnumSet; @@ -684,9 +683,6 @@ public class TrackDataHub { final TrackDataListener.ProviderState state; if (!hasProviderEnabled) { state = ProviderState.DISABLED; - - // Give a global warning about this state. - Toast.makeText(context, R.string.error_no_gps_location_provider, Toast.LENGTH_LONG).show(); } else if (!hasFix) { state = ProviderState.NO_FIX; } else if (!hasGoodFix) { @@ -824,10 +820,6 @@ public class TrackDataHub { public void run() { for (TrackDataListener listener : listeners) { listener.onSelectedTrackChanged(track, isRecordingSelected()); - - if (track != null) { - listener.onTrackUpdated(track); - } } } }); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java index 8fd6f2e41..5dbd57c6b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java @@ -180,6 +180,11 @@ public class SendToFusionTables implements Runnable { // Get the track meta-data Track track = providerUtils.getTrack(trackId); + if (track == null) { + Log.w(Constants.TAG, "Cannot get track."); + return; + } + String originalDescription = track.getDescription(); // Create a new table: @@ -208,7 +213,6 @@ public class SendToFusionTables implements Runnable { Log.d(Constants.TAG, "SendToFusionTables: Done: " + success); progressIndicator.setProgressValue(PROGRESS_COMPLETE); } finally { - final boolean finalSuccess = success; final int finalStatusMessageId = statusMessageId; context.runOnUiThread(new Runnable() { @@ -305,7 +309,7 @@ public class SendToFusionTables implements Runnable { Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false); try { - if (!locationsCursor.moveToFirst()) { + if (locationsCursor == null || !locationsCursor.moveToFirst()) { Log.w(Constants.TAG, "Unable to get any points to upload"); return false; } @@ -392,7 +396,9 @@ public class SendToFusionTables implements Runnable { return true; } finally { - locationsCursor.close(); + if (locationsCursor != null) { + locationsCursor.close(); + } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java index ffc895da6..f451078d3 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java @@ -1,12 +1,12 @@ /* * Copyright 2008 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 @@ -76,7 +76,7 @@ public class SendToMyMaps implements Runnable { public interface OnSendCompletedListener { void onSendCompleted(String mapId, boolean success, int statusMessage); } - + public SendToMyMaps(Activity context, String mapId, AuthManager auth, long trackId, ProgressIndicator progressIndicator, OnSendCompletedListener onCompletion) { @@ -103,15 +103,20 @@ public class SendToMyMaps implements Runnable { progressIndicator.setProgressValue(1); progressIndicator.setProgressMessage( R.string.progress_message_reading_track); - + // Get the track meta-data Track track = providerUtils.getTrack(trackId); + if (track == null) { + Log.w(Constants.TAG, "Cannot get track."); + return; + } + String originalDescription = track.getDescription(); track.setDescription("

" + track.getDescription() + "

" + stringUtils.generateTrackDescription(track, null, null) + "

"); mapsClient = new MapsFacade(context, auth); - + // Create a new map if necessary: boolean isNewMap = mapId.equals(NEW_MAP_ID); if (isNewMap) { @@ -166,7 +171,7 @@ public class SendToMyMaps implements Runnable { Log.w(TAG, "SendToMyMaps: upload waypoints failed."); } } - + if (success) { statusMessageId = isNewMap ? R.string.status_new_mymap_has_been_created @@ -178,7 +183,7 @@ public class SendToMyMaps implements Runnable { if (mapsClient != null) { mapsClient.cleanUp(); } - + final boolean finalSuccess = success; final int finalStatusMessageId = statusMessageId; context.runOnUiThread(new Runnable() { @@ -206,17 +211,17 @@ public class SendToMyMaps implements Runnable { Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false); try { - if (!locationsCursor.moveToFirst()) { + if (locationsCursor == null || !locationsCursor.moveToFirst()) { Log.w(TAG, "Unable to get any points to upload"); return false; } - + totalLocationsRead = 0; totalLocationsPrepared = 0; totalLocationsUploaded = 0; totalLocations = locationsCursor.getCount(); totalSegmentsUploaded = 0; - + // Limit the number of elevation readings. Ideally we would want around 250. int elevationSamplingFrequency = Math.max(1, (int) (totalLocations / 250.0)); @@ -224,12 +229,12 @@ public class SendToMyMaps implements Runnable { "Using elevation sampling factor: " + elevationSamplingFrequency + " on " + totalLocations); double totalDistance = 0; - + Vector distances = new Vector(); Vector elevations = new Vector(); DoubleBuffer elevationBuffer = new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR); - + List locations = new ArrayList(MAX_POINTS_PER_UPLOAD); progressIndicator.setProgressMessage( R.string.progress_message_reading_track); @@ -238,15 +243,15 @@ public class SendToMyMaps implements Runnable { if (totalLocationsRead % 100 == 0) { updateProgress(); } - + Location loc = providerUtils.createLocation(locationsCursor); locations.add(loc); - + if (totalLocationsRead == 0) { // Put a marker at the first point of the first valid segment: mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true); } - + // Add to the elevation profile. if (loc != null && LocationUtils.isValidLocation(loc)) { // All points go into the smoothing buffer... @@ -256,19 +261,19 @@ public class SendToMyMaps implements Runnable { double dist = lastLocation.distanceTo(loc); totalDistance += dist; } - + // ...but only a few points are really used to keep the url short. if (totalLocationsRead % elevationSamplingFrequency == 0) { distances.add(totalDistance); elevations.add(elevationBuffer.getAverage()); } } - + // If the location was not valid, it's a segment split, so make sure the // distance between the previous segment and the new one is not accounted // for in the next iteration. lastLocation = loc; - + // Every now and then, upload the accumulated points if (totalLocationsRead % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD - 1) { @@ -276,28 +281,30 @@ public class SendToMyMaps implements Runnable { return false; } } - + totalLocationsRead++; } while (locationsCursor.moveToNext()); - + // Do a final upload with what's left if (!prepareAndUploadPoints(track, locations)) { return false; } - + // Put an end marker at the last point of the last valid segment: if (lastLocation != null) { track.setDescription("

" + originalDescription + "

" + stringUtils.generateTrackDescription( track, distances, elevations) + "

"); - return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), + return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), lastLocation, false); } - + return true; } finally { - locationsCursor.close(); + if (locationsCursor != null) { + locationsCursor.close(); + } } } @@ -306,17 +313,17 @@ public class SendToMyMaps implements Runnable { progressIndicator.setProgressMessage( R.string.progress_message_preparing_track); updateProgress(); - + int numLocations = locations.size(); if (numLocations < 2) { Log.d(TAG, "Not preparing/uploading too few points"); totalLocationsUploaded += numLocations; return true; } - + // Prepare/pre-process the points ArrayList splitTracks = prepareLocations(track, locations); - + // Start uploading them progressIndicator.setProgressMessage( R.string.progress_message_sending_mymaps); @@ -330,7 +337,7 @@ public class SendToMyMaps implements Runnable { Log.d(TAG, "SendToMyMaps: Prepared feature for upload w/ " + splitTrack.getLocations().size() + " points."); - + // Transmit tracks via GData feed: // ------------------------------- Log.d(TAG, @@ -340,7 +347,7 @@ public class SendToMyMaps implements Runnable { return false; } } - + locations.clear(); totalLocationsUploaded += numLocations; updateProgress(); @@ -358,7 +365,7 @@ public class SendToMyMaps implements Runnable { private ArrayList prepareLocations( Track track, Iterable locations) { ArrayList splitTracks = new ArrayList(); - + // Create segments from each full track: Track segment = new Track(); TripStatistics segmentStats = segment.getStatistics(); @@ -377,11 +384,11 @@ public class SendToMyMaps implements Runnable { if (loc.getLatitude() > 90) { startNewTrackSegment = true; } - + if (startNewTrackSegment) { // Close up the last segment. prepareTrackSegment(segment, splitTracks); - + Log.d(TAG, "MyTracksSendToMyMaps: Starting new track segment..."); startNewTrackSegment = false; @@ -391,7 +398,7 @@ public class SendToMyMaps implements Runnable { segment.setDescription(/* track.getDescription() */ ""); segment.setCategory(track.getCategory()); } - + if (loc.getLatitude() <= 90) { segment.addLocation(loc); if (segmentStats.getStartTime() < 0) { @@ -400,9 +407,9 @@ public class SendToMyMaps implements Runnable { } totalLocationsPrepared++; } - + prepareTrackSegment(segment, splitTracks); - + return splitTracks; } @@ -425,13 +432,13 @@ public class SendToMyMaps implements Runnable { && segment.getLocations().size() > 0) { segmentStats.setStopTime(segment.getLocations().size() - 1); } - + /* * Decimate to 2 meter precision. Mapshop doesn't like too many * points: */ LocationUtils.decimate(segment, 2.0); - + /* It the track still has > 500 points, split it in pieces: */ if (segment.getLocations().size() > 500) { splitTracks.addAll(LocationUtils.split(segment, 500)); diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java index f17e99513..b1a08d6ee 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TcxTrackWriter.java @@ -187,7 +187,7 @@ public class TcxTrackWriter implements TrackFormatWriter { && sensorData.getCadence().getState() == Sensor.SensorState.SENDING && sensorData.getCadence().hasValue()) { pw.print(" "); - pw.print(sensorData.getCadence().getValue()); + pw.print(Math.min(254, sensorData.getCadence().getValue())); pw.println(""); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java index 5aabf6133..49f1f9f01 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java @@ -16,10 +16,13 @@ package com.google.android.apps.mytracks.io.file; +import static com.google.android.apps.mytracks.Constants.TAG; + import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import android.content.Context; +import android.util.Log; /** * A factory to produce track writers for any format. @@ -90,6 +93,11 @@ public class TrackWriterFactory { MyTracksProviderUtils providerUtils, long trackId, TrackFileFormat format) { Track track = providerUtils.getTrack(trackId); + if (track == null) { + Log.w(TAG, "Trying to create a writer for an invalid track, id=" + trackId); + return null; + } + return newWriter(context, providerUtils, track, format); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java index b8d8f4a89..28708bdb4 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/sendtogoogle/SendActivity.java @@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.io.sendtogoogle; import static com.google.android.apps.mytracks.Constants.TAG; import com.google.android.accounts.Account; +import com.google.android.apps.analytics.GoogleAnalyticsTracker; import com.google.android.apps.mytracks.AccountChooser; import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.DialogManager; @@ -34,6 +35,7 @@ import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedLis import com.google.android.apps.mytracks.io.SendToMyMaps; import com.google.android.apps.mytracks.io.mymaps.MapsFacade; import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants; +import com.google.android.apps.mytracks.util.SystemUtils; import com.google.android.apps.mytracks.util.UriUtils; import com.google.android.maps.mytracks.R; @@ -69,6 +71,8 @@ public class SendActivity extends Activity implements ProgressIndicator { private MyTracksProviderUtils providerUtils; private SharedPreferences sharedPreferences; + private GoogleAnalyticsTracker tracker; + // Authentication private AuthManager lastAuth; private final HashMap authMap = @@ -111,6 +115,11 @@ public class SendActivity extends Activity implements ProgressIndicator { super.onStart(); resetState(); + tracker = GoogleAnalyticsTracker.getInstance(); + // Start the tracker in manual dispatch mode... + tracker.start(getString(R.string.google_analytics_id), getApplicationContext()); + tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this)); + Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); @@ -141,11 +150,19 @@ public class SendActivity extends Activity implements ProgressIndicator { sendDialog.show(); } + @Override + protected void onStop() { + tracker.dispatch(); + tracker.stop(); + + super.onStop(); + } + /** * Initiates the process to send tracks to google. * This is called once the user has selected sending options via the * SendToGoogleDialog. - * + * * TODO: Change this whole flow to an actual state machine. */ private void doSend() { @@ -193,6 +210,8 @@ public class SendActivity extends Activity implements ProgressIndicator { } private void sendToGoogleMaps(String mapId) { + tracker.trackPageView("/send/maps"); + SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() { @Override public void onSendCompleted(String mapId, boolean success, int statusMessage) { @@ -247,6 +266,8 @@ public class SendActivity extends Activity implements ProgressIndicator { } private void sendToFusionTables() { + tracker.trackPageView("/send/fusion_tables"); + OnSendCompletedListener onCompletion = new OnSendCompletedListener() { @Override public void onSendCompleted(String tableId, boolean success, @@ -310,6 +331,8 @@ public class SendActivity extends Activity implements ProgressIndicator { private void sendToGoogleDocs() { Log.d(TAG, "Sending to Docs...."); + tracker.trackPageView("/send/docs"); + setProgressValue(50); setProgressMessage(R.string.progress_message_sending_docs); final SendToDocs sender = new SendToDocs(this, @@ -319,7 +342,7 @@ public class SendActivity extends Activity implements ProgressIndicator { Runnable onCompletion = new Runnable() { public void run() { setProgressValue(100); - + // TODO: Use this message sendToDocsMessage = sender.getStatusMessage(); sendToDocsSuccess = sender.wasSuccess(); @@ -336,6 +359,8 @@ public class SendActivity extends Activity implements ProgressIndicator { } private void onSendToGoogleDone() { + tracker.dispatch(); + final boolean sentToMyMaps = sendDialog.getSendToMyMaps(); final boolean sentToFusionTables = sendDialog.getSendToFusionTables(); List results = makeSendToGoogleResults(); diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java index 64905da79..971bdd60f 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -1,12 +1,12 @@ /* * Copyright 2008 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 @@ -40,7 +40,7 @@ import java.util.NoSuchElementException; public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { private final ContentResolver contentResolver; - + private int defaultCursorBatchSize = 2000; public MyTracksProviderUtilsImpl(ContentResolver contentResolver) { @@ -199,12 +199,12 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { public final int idxLatitude; public final int idxLongitude; public final int idxAltitude; - public final int idxTime; - public final int idxBearing; + public final int idxTime; + public final int idxBearing; public final int idxAccuracy; public final int idxSpeed; - public final int idxSensor; - + public final int idxSensor; + public CachedTrackColumnIndices(Cursor cursor) { idxId = cursor.getColumnIndex(TrackPointsColumns._ID); idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); @@ -217,11 +217,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR); } } - + private void fillLocation(Cursor cursor, CachedTrackColumnIndices columnIndices, Location location) { location.reset(); - + if (!cursor.isNull(columnIndices.idxLatitude)) { location.setLatitude(1. * cursor.getInt(columnIndices.idxLatitude) / 1E6); } @@ -256,7 +256,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } } } - + @Override public void fillLocation(Cursor cursor, Location location) { CachedTrackColumnIndices columnIndicies = new CachedTrackColumnIndices(cursor); @@ -660,6 +660,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Waypoint getFirstWaypoint(long trackId) { + if (trackId < 0) { + return null; + } + Cursor cursor = contentResolver.query( WaypointsColumns.CONTENT_URI, null /*projection*/, @@ -682,6 +686,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Waypoint getWaypoint(long waypointId) { + if (waypointId < 0) { + return null; + } + Cursor cursor = contentResolver.query( WaypointsColumns.CONTENT_URI, null /*projection*/, @@ -704,6 +712,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public long getLastLocationId(long trackId) { + if (trackId < 0) { + return -1; + } + final String[] projection = {"_id"}; Cursor cursor = contentResolver.query( TrackPointsColumns.CONTENT_URI, @@ -728,6 +740,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public long getFirstWaypointId(long trackId) { + if (trackId < 0) { + return -1; + } + final String[] projection = {"_id"}; Cursor cursor = contentResolver.query( WaypointsColumns.CONTENT_URI, @@ -752,6 +768,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public long getLastWaypointId(long trackId) { + if (trackId < 0) { + return -1; + } + final String[] projection = {"_id"}; Cursor cursor = contentResolver.query( WaypointsColumns.CONTENT_URI, @@ -815,6 +835,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Location getLocation(long id) { + if (id < 0) { + return null; + } + String selection = TrackPointsColumns._ID + "=" + id; return findLocationBy(selection); } @@ -822,7 +846,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Cursor getLocationsCursor(long trackId, long minTrackPointId, int maxLocations, boolean descending) { - String selection; + if (trackId < 0 || maxLocations < 1) { + return null; + } + + String selection; if (minTrackPointId >= 0) { selection = String.format("%s=%d AND %s%s%d", TrackPointsColumns.TRACKID, trackId, TrackPointsColumns._ID, @@ -842,6 +870,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Cursor getWaypointsCursor(long trackId, long minWaypointId, int maxWaypoints) { + if (trackId < 0 || maxWaypoints < 1) { + return null; + } + String selection; if (minWaypointId > 0) { selection = String.format("%s=%d AND %s>=%d", @@ -863,6 +895,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public Track getTrack(long id) { + if (id < 0) { + return null; + } + String select = TracksColumns._ID + "=" + id; return findTrackBy(select); } @@ -929,6 +965,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { @Override public boolean trackExists(long id) { + if (id < 0) { + return false; + } + Cursor cursor = null; try { final String[] projection = { TracksColumns._ID }; @@ -967,7 +1007,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { private Cursor cursor = getCursor(startTrackPointId); private final CachedTrackColumnIndices columnIndices = cursor != null ? new CachedTrackColumnIndices(cursor) : null; - + private Cursor getCursor(long trackPointId) { return getLocationsCursor(trackId, trackPointId, defaultCursorBatchSize, descending); } @@ -979,7 +1019,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { cursor = getCursor(pointId); return cursor != null; } - + @Override public long getLocationId() { return lastTrackPointId; @@ -999,7 +1039,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { return cursor.getCount() == defaultCursorBatchSize && advanceCursorToNextBatch() && !cursor.isAfterLast(); } - + return true; } @@ -1009,11 +1049,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { !(cursor.moveToNext() || advanceCursorToNextBatch() || cursor.moveToNext())) { throw new NoSuchElementException(); } - + lastTrackPointId = cursor.getLong(columnIndices.idxId); Location location = locationFactory.createLocation(); fillLocation(cursor, columnIndices, location); - + return location; } @@ -1031,7 +1071,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { } }; } - + // @VisibleForTesting void setDefaultCursorBatchSize(int defaultCursorBatchSize) { this.defaultCursorBatchSize = defaultCursorBatchSize;