diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java new file mode 100644 index 000000000..b0543e6f7 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java @@ -0,0 +1,34 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks; + +/** + * Constants used by the MyTracks application. + * + * @author Leif Hendrik Wilden + */ +public abstract class MyTracksConstants { + + /** + * Should be used by all log statements + */ + public static final String TAG = "MyTracks"; + + /** + * This is an abstract utility class. + */ + private MyTracksConstants() { } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java b/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java new file mode 100644 index 000000000..a2dfcc66c --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2010 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.content; + +import java.util.Vector; + +/** + * An interface for an object that can generate descriptions of waypoints. + * + * @author Sandor Dornbush + */ +public interface DescriptionGenerator { + + /** + * Generate a description of the waypoint. + */ + public String generateWaypointDescription(Waypoint waypoint); + + /** + * Generates a description for a track (with information about the + * statistics). + * + * @param track the track + * @return a track description + */ + public String generateTrackDescription(Track track, Vector distances, + Vector elevations); +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java new file mode 100644 index 000000000..587bc1257 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java @@ -0,0 +1,363 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +/** + * A provider that handles recorded (GPS) tracks and their track points. + * + * @author Leif Hendrik Wilden + */ +public class MyTracksProvider extends ContentProvider { + + private static final String DATABASE_NAME = "mytracks.db"; + private static final int DATABASE_VERSION = 17; + private static final int TRACKPOINTS = 1; + private static final int TRACKPOINTS_ID = 2; + private static final int TRACKS = 3; + private static final int TRACKS_ID = 4; + private static final int WAYPOINTS = 5; + private static final int WAYPOINTS_ID = 6; + private static final String TRACKPOINTS_TABLE = "trackpoints"; + private static final String TRACKS_TABLE = "tracks"; + private static final String WAYPOINTS_TABLE = "waypoints"; + public static final String TAG = "MyTracksProvider"; + + /** + * Helper which creates or upgrades the database if necessary. + */ + private static class DatabaseHelper extends SQLiteOpenHelper { + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TRACKPOINTS_TABLE + " (" + + TrackPointsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + TrackPointsColumns.TRACKID + " INTEGER, " + + TrackPointsColumns.LONGITUDE + " INTEGER, " + + TrackPointsColumns.LATITUDE + " INTEGER, " + + TrackPointsColumns.TIME + " INTEGER, " + + TrackPointsColumns.ALTITUDE + " FLOAT, " + + TrackPointsColumns.ACCURACY + " FLOAT, " + + TrackPointsColumns.SPEED + " FLOAT, " + + TrackPointsColumns.BEARING + " FLOAT);"); + db.execSQL("CREATE TABLE " + TRACKS_TABLE + " (" + + TracksColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + TracksColumns.NAME + " STRING, " + + TracksColumns.DESCRIPTION + " STRING, " + + TracksColumns.CATEGORY + " STRING, " + + TracksColumns.STARTID + " INTEGER, " + + TracksColumns.STOPID + " INTEGER, " + + TracksColumns.STARTTIME + " INTEGER, " + + TracksColumns.STOPTIME + " INTEGER, " + + TracksColumns.NUMPOINTS + " INTEGER, " + + TracksColumns.TOTALDISTANCE + " FLOAT, " + + TracksColumns.TOTALTIME + " INTEGER, " + + TracksColumns.MOVINGTIME + " INTEGER, " + + TracksColumns.MINLAT + " INTEGER, " + + TracksColumns.MAXLAT + " INTEGER, " + + TracksColumns.MINLON + " INTEGER, " + + TracksColumns.MAXLON + " INTEGER, " + + TracksColumns.AVGSPEED + " FLOAT, " + + TracksColumns.AVGMOVINGSPEED + " FLOAT, " + + TracksColumns.MAXSPEED + " FLOAT, " + + TracksColumns.MINELEVATION + " FLOAT, " + + TracksColumns.MAXELEVATION + " FLOAT, " + + TracksColumns.ELEVATIONGAIN + " FLOAT, " + + TracksColumns.MINGRADE + " FLOAT, " + + TracksColumns.MAXGRADE + " FLOAT, " + + TracksColumns.MAPID + " STRING);"); + db.execSQL("CREATE TABLE " + WAYPOINTS_TABLE + " (" + + WaypointsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + WaypointsColumns.NAME + " STRING, " + + WaypointsColumns.DESCRIPTION + " STRING, " + + WaypointsColumns.CATEGORY + " STRING, " + + WaypointsColumns.ICON + " STRING, " + + WaypointsColumns.TRACKID + " INTEGER, " + + WaypointsColumns.TYPE + " INTEGER, " + + WaypointsColumns.LENGTH + " FLOAT, " + + WaypointsColumns.DURATION + " INTEGER, " + + WaypointsColumns.STARTTIME + " INTEGER, " + + WaypointsColumns.STARTID + " INTEGER, " + + WaypointsColumns.STOPID + " INTEGER, " + + WaypointsColumns.LONGITUDE + " INTEGER, " + + WaypointsColumns.LATITUDE + " INTEGER, " + + WaypointsColumns.TIME + " INTEGER, " + + WaypointsColumns.ALTITUDE + " FLOAT, " + + WaypointsColumns.ACCURACY + " FLOAT, " + + WaypointsColumns.SPEED + " FLOAT, " + + WaypointsColumns.BEARING + " FLOAT, " + + WaypointsColumns.TOTALDISTANCE + " FLOAT, " + + WaypointsColumns.TOTALTIME + " INTEGER, " + + WaypointsColumns.MOVINGTIME + " INTEGER, " + + WaypointsColumns.AVGSPEED + " FLOAT, " + + WaypointsColumns.AVGMOVINGSPEED + " FLOAT, " + + WaypointsColumns.MAXSPEED + " FLOAT, " + + WaypointsColumns.MINELEVATION + " FLOAT, " + + WaypointsColumns.MAXELEVATION + " FLOAT, " + + WaypointsColumns.ELEVATIONGAIN + " FLOAT, " + + WaypointsColumns.MINGRADE + " FLOAT, " + + WaypointsColumns.MAXGRADE + " FLOAT);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + + newVersion + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS " + TRACKPOINTS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + TRACKS_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + WAYPOINTS_TABLE); + onCreate(db); + } + } + + private final UriMatcher urlMatcher; + + private SQLiteDatabase db; + + public MyTracksProvider() { + urlMatcher = new UriMatcher(UriMatcher.NO_MATCH); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, + "trackpoints", TRACKPOINTS); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, + "trackpoints/#", TRACKPOINTS_ID); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks", TRACKS); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "tracks/#", TRACKS_ID); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, "waypoints", WAYPOINTS); + urlMatcher.addURI(MyTracksProviderUtils.AUTHORITY, + "waypoints/#", WAYPOINTS_ID); + } + + @Override + public boolean onCreate() { + DatabaseHelper dbHelper = new DatabaseHelper(getContext()); + db = dbHelper.getWritableDatabase(); + return db != null; + } + + @Override + public int delete(Uri url, String where, String[] selectionArgs) { + if (urlMatcher.match(url) == TRACKPOINTS) { + Log.w(MyTracksProvider.TAG, "provider trackpoints delete!"); + int count = db.delete(TRACKPOINTS_TABLE, where, selectionArgs); + getContext().getContentResolver().notifyChange(url, null, true); + return count; + } else if (urlMatcher.match(url) == TRACKS) { + Log.w(MyTracksProvider.TAG, "provider track delete!"); + int count = db.delete(TRACKS_TABLE, where, selectionArgs); + getContext().getContentResolver().notifyChange(url, null, true); + return count; + } else if (urlMatcher.match(url) == WAYPOINTS) { + Log.w(MyTracksProvider.TAG, "provider waypoint delete!"); + int count = db.delete(WAYPOINTS_TABLE, where, selectionArgs); + getContext().getContentResolver().notifyChange(url, null, true); + return count; + } else { + throw new IllegalArgumentException("Unknown URL " + url); + } + } + + @Override + public String getType(Uri url) { + switch (urlMatcher.match(url)) { + case TRACKPOINTS: + return TrackPointsColumns.CONTENT_TYPE; + case TRACKPOINTS_ID: + return TrackPointsColumns.CONTENT_ITEMTYPE; + case TRACKS: + return TracksColumns.CONTENT_TYPE; + case TRACKS_ID: + return TracksColumns.CONTENT_ITEMTYPE; + case WAYPOINTS: + return WaypointsColumns.CONTENT_TYPE; + case WAYPOINTS_ID: + return WaypointsColumns.CONTENT_ITEMTYPE; + default: + throw new IllegalArgumentException("Unknown URL " + url); + } + } + + @Override + public Uri insert(Uri url, ContentValues initialValues) { + Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert"); + ContentValues values; + if (initialValues != null) { + values = initialValues; + } else { + values = new ContentValues(); + } + if (urlMatcher.match(url) == TRACKPOINTS) { + return insertTrackPoint(url, values); + } else if (urlMatcher.match(url) == TRACKS) { + return insertTrack(url, values); + } else if (urlMatcher.match(url) == WAYPOINTS) { + return insertWaypoint(url, values); + } else { + throw new IllegalArgumentException("Unknown URL " + url); + } + } + + private Uri insertTrackPoint(Uri url, ContentValues values) { + Log.d(MyTracksProvider.TAG, "MyTracksProvider.insertTrackPoint"); + boolean hasLat = values.containsKey(TrackPointsColumns.LATITUDE); + boolean hasLong = values.containsKey(TrackPointsColumns.LONGITUDE); + boolean hasTime = values.containsKey(TrackPointsColumns.TIME); + if (!hasLat || !hasLong || !hasTime) { + throw new IllegalArgumentException( + "Latitude, longitude, and time values are required."); + } + long rowId = db.insert(TRACKPOINTS_TABLE, TrackPointsColumns._ID, values); + if (rowId >= 0) { + Uri uri = ContentUris.appendId( + TrackPointsColumns.CONTENT_URI.buildUpon(), rowId).build(); + getContext().getContentResolver().notifyChange(url, null, true); + return uri; + } + throw new SQLiteException("Failed to insert row into " + url); + } + + private Uri insertTrack(Uri url, ContentValues values) { + boolean hasStartTime = values.containsKey(TracksColumns.STARTTIME); + boolean hasStartId = values.containsKey(TracksColumns.STARTID); + if (!hasStartTime || !hasStartId) { + throw new IllegalArgumentException( + "Both start time and start id values are required."); + } + long rowId = db.insert(TRACKS_TABLE, TracksColumns._ID, values); + if (rowId > 0) { + Uri uri = ContentUris.appendId( + TracksColumns.CONTENT_URI.buildUpon(), rowId).build(); + getContext().getContentResolver().notifyChange(url, null, true); + return uri; + } + throw new SQLException("Failed to insert row into " + url); + } + + private Uri insertWaypoint(Uri url, ContentValues values) { + long rowId = db.insert(WAYPOINTS_TABLE, WaypointsColumns._ID, values); + if (rowId > 0) { + Uri uri = ContentUris.appendId( + WaypointsColumns.CONTENT_URI.buildUpon(), rowId).build(); + getContext().getContentResolver().notifyChange(url, null, true); + return uri; + } + throw new SQLException("Failed to insert row into " + url); + } + + @Override + public Cursor query( + Uri url, String[] projection, String selection, String[] selectionArgs, + String sort) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + int match = urlMatcher.match(url); + String sortOrder = null; + if (match == TRACKPOINTS) { + qb.setTables(TRACKPOINTS_TABLE); + if (sort != null) { + sortOrder = sort; + } else { + sortOrder = TrackPointsColumns.DEFAULT_SORT_ORDER; + } + } else if (match == TRACKPOINTS_ID) { + qb.setTables(TRACKPOINTS_TABLE); + qb.appendWhere("_id=" + url.getPathSegments().get(1)); + } else if (match == TRACKS) { + qb.setTables(TRACKS_TABLE); + if (sort != null) { + sortOrder = sort; + } else { + sortOrder = TracksColumns.DEFAULT_SORT_ORDER; + } + } else if (match == TRACKS_ID) { + qb.setTables(TRACKS_TABLE); + qb.appendWhere("_id=" + url.getPathSegments().get(1)); + } else if (match == WAYPOINTS) { + qb.setTables(WAYPOINTS_TABLE); + if (sort != null) { + sortOrder = sort; + } else { + sortOrder = WaypointsColumns.DEFAULT_SORT_ORDER; + } + } else if (match == WAYPOINTS_ID) { + qb.setTables(WAYPOINTS_TABLE); + qb.appendWhere("_id=" + url.getPathSegments().get(1)); + } else { + throw new IllegalArgumentException("Unknown URL " + url); + } + + Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, + sortOrder); + c.setNotificationUri(getContext().getContentResolver(), url); + return c; + } + + @Override + public int update(Uri url, ContentValues values, String where, + String[] selectionArgs) { + int count; + int match = urlMatcher.match(url); + if (match == TRACKPOINTS) { + count = db.update(TRACKPOINTS_TABLE, values, where, selectionArgs); + } else if (match == TRACKPOINTS_ID) { + String segment = url.getPathSegments().get(1); + count = db.update(TRACKPOINTS_TABLE, values, "_id=" + segment + + (!TextUtils.isEmpty(where) + ? " AND (" + where + ')' + : ""), + selectionArgs); + } else if (match == TRACKS) { + count = db.update(TRACKS_TABLE, values, where, selectionArgs); + } else if (match == TRACKS_ID) { + String segment = url.getPathSegments().get(1); + count = db.update(TRACKS_TABLE, values, "_id=" + segment + + (!TextUtils.isEmpty(where) + ? " AND (" + where + ')' + : ""), + selectionArgs); + } else if (match == WAYPOINTS) { + count = db.update(WAYPOINTS_TABLE, values, where, selectionArgs); + } else if (match == WAYPOINTS_ID) { + String segment = url.getPathSegments().get(1); + count = db.update(WAYPOINTS_TABLE, values, "_id=" + segment + + (!TextUtils.isEmpty(where) + ? " AND (" + where + ')' + : ""), + selectionArgs); + } else { + throw new IllegalArgumentException("Unknown URL " + url); + } + getContext().getContentResolver().notifyChange(url, null, true); + return count; + } + +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java new file mode 100644 index 000000000..557dc18de --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java @@ -0,0 +1,321 @@ +/* + * Copyright 2010 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.content; + +import android.content.Context; +import android.database.Cursor; +import android.location.Location; +import android.net.Uri; + +/** + * Utility to access data from the mytracks content provider. + * + * @author Rodrigo Damazio + */ +public interface MyTracksProviderUtils { + + /** + * Authority (first part of URI) for the MyTracks content provider: + */ + public static final String AUTHORITY = "com.google.android.maps.mytracks"; + + /** + * Deletes all tracks (including track points) from the provider. + */ + void deleteAllTracks(); + + /** + * Deletes a track with the given track id. + * + * @param trackId the unique track id + */ + void deleteTrack(long trackId); + + /** + * Deletes a way point with the given way point id. + * This will also correct the next statistics way point after the deleted one + * to reflect the deletion. + * The generator is needed to stitch together statistics waypoints. + * + * @param waypointId the unique way point id + * @param descriptionGenerator the class to generate descriptions + */ + void deleteWaypoint(long waypointId, + DescriptionGenerator descriptionGenerator); + + /** + * Finds the next statistics waypoint after the given waypoint. + * + * @param waypoint a given waypoint + * @return the next statistics waypoint, or null if none found + */ + Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint); + + /** + * Updates the waypoint in the provider. + * + * @param waypoint + * @return true if successful + */ + boolean updateWaypoint(Waypoint waypoint); + + /** + * Finds the last recorded location from the location provider. + * + * @return the last location, or null if no locations available + */ + Location getLastLocation(); + + /** + * Finds the first recorded waypoint for a given track from the location + * provider. + * This is a special waypoint that holds the stats for current segment. + * + * @param trackId the id of the track the waypoint belongs to + * @return the first waypoint, or null if no waypoints available + */ + Waypoint getFirstWaypoint(long trackId); + + /** + * Finds the given waypoint from the location provider. + * + * @param waypointId + * @return the waypoint, or null if it does not exist + */ + Waypoint getWaypoint(long waypointId); + + /** + * Finds the last recorded location id from the track points provider. + * + * @param trackId find last location on this track + * @return the location id, or -1 if no locations available + */ + long getLastLocationId(long trackId); + + /** + * Finds the id of the 1st waypoint for a given track. + * The 1st waypoint is special as it contains the stats for the current + * segment. + * + * @param trackId find last location on this track + * @return the waypoint id, or -1 if no waypoints are available + */ + long getFirstWaypointId(long trackId); + + /** + * Finds the id of the 1st waypoint for a given track. + * The 1st waypoint is special as it contains the stats for the current + * segment. + * + * @param trackId find last location on this track + * @return the waypoint id, or -1 if no waypoints are available + */ + long getLastWaypointId(long trackId); + + /** + * Finds the last recorded track from the track provider. + * + * @return the last track, or null if no tracks available + */ + Track getLastTrack(); + + /** + * Finds the last recorded track id from the tracks provider. + * + * @return the track id, or -1 if no tracks available + */ + long getLastTrackId(); + + /** + * Finds a location by given unique id. + * + * @param id the desired id + * @return a Location object, or null if not found + */ + Location getLocation(long id); + + /** + * Creates a cursor over the locations in the track points provider which + * iterates over a given range of unique ids. + * Caller gets to own the returned cursor. Don't forget to close it. + * + * @param trackId the id of the track for which to get the points + * @param minTrackPointId the minimum id for the track points + * @param maxLocations maximum number of locations retrieved + * @param descending if true the results will be returned in descending id + * order (latest location first) + * @return A cursor over the selected range of locations + */ + Cursor getLocationsCursor(long trackId, long minTrackPointId, + int maxLocations, boolean descending); + + /** + * Creates a cursor over the waypoints of a track. + * Caller gets to own the returned cursor. Don't forget to close it. + * + * @param trackId the id of the track for which to get the points + * @param minWaypointId the minimum id for the track points + * @param maxWaypoints the maximum number of waypoints to return + * @return A cursor over the selected range of locations + */ + Cursor getWaypointsCursor(long trackId, long minWaypointId, + long maxWaypoints); + + /** + * Finds a track by given unique track id. + * Note that the returned track object does not have any track points + * attached. Use {@link #getTrackPoints(Track, int)} to load the track points. + * + * @param id desired unique track id + * @return a Track object, or null if not found + */ + Track getTrack(long id); + + /** + * Loads the track points for a given track. + * + * @param track assumes that startId and stopId are filled in correctly + * @param maxPoints maximum number of points to load, or -1 for no limit + * (oldest ones will be discarded) + * @return the id of the last location in the track + */ + long getTrackPoints(Track track, int maxPoints); + + /** + * Fetches some number of locations for the given track. + * + * This is designed to be used to stream through large tracks without loading + * all points into memory. + * + * @param track to load locations for + * @param buffer an array of locations to fill + */ + void getTrackPoints(Track track, TrackBuffer buffer); + + /** + * Creates a cursor over the tracks provider with a given selection. + * Caller gets to own the returned cursor. Don't forget to close it. + * + * @param selection a given selection + * @return a cursor of the selected tracks + */ + Cursor getTracksCursor(String selection); + + /** + * Inserts a track in the tracks provider. + * Note: This does not insert any track points. + * Use {@link #insertTrackPoint(Location, long)} to insert them. + * + * @param track the track to insert + * @return the content provider URI for the inserted track + */ + Uri insertTrack(Track track); + + /** + * Inserts a track point in the tracks provider. + * + * @param location the location to insert + * @return the content provider URI for the inserted track point + */ + Uri insertTrackPoint(Location location, long trackId); + + /** + * Inserts a waypoint in the provider. + * + * @param waypoint the waypoint to insert + * @return the content provider URI for the inserted track + */ + Uri insertWaypoint(Waypoint waypoint); + + /** + * Inserts a track including its track points in the provider. + * + * @param track the track to insert + * @return the content provider URI for the inserted track + */ + Uri insertTrackAndTrackPoints(Track track); + + /** + * Tests if a track with given id exists. + * + * @param id the unique id + * @return true if track exists + */ + boolean trackExists(long id); + + /** + * Updates a track in the content provider. + * Note: This will not update any track points. + * + * @param track a given track + */ + void updateTrack(Track track); + + /** + * Creates a location object from a given cursor. + * + * @param cursor a cursor pointing at a db or provider with locations + * @return a new location object + */ + public Location createLocation(Cursor cursor); + + /** + * Creates a waypoint object from a given cursor. + * + * @param cursor a cursor pointing at a db or provider with waypoints + * @return a new waypoint object + */ + public Waypoint createWaypoint(Cursor cursor); + + /** + * A factory which can produce instances of {@link MyTracksProviderUtils}, + * and can be overridden in tests (a.k.a. poor man's guice). + */ + public static class Factory { + private static Factory instance = new Factory(); + + /** + * Creates and returns an instance of {@link MyTracksProviderUtils} which + * uses the given context to access its data. + */ + public static MyTracksProviderUtils get(Context context) { + return instance.newForContext(context); + } + + /** + * Returns the global instance of this factory. + */ + public static Factory getInstance() { + return instance; + } + + /** + * Overrides the global instance for this factory, to be used for testing. + * If used, don't forget to set it back to the original value after the + * test is run. + */ + public static void overrideInstance(Factory factory) { + instance = factory; + } + + /** + * Creates an instance of {@link MyTracksProviderUtils}. + */ + protected MyTracksProviderUtils newForContext(Context context) { + return new MyTracksProviderUtilsImpl(context); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java new file mode 100644 index 000000000..1577225db --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -0,0 +1,975 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.location.Location; +import android.net.Uri; +import android.util.Log; + +/** + * Helper class providing easy access to locations and tracks in the + * MyTracksProvider. All static members. + * + * @author Leif Hendrik Wilden + */ +public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils { + + private final Context context; + + public MyTracksProviderUtilsImpl(Context context) { + this.context = context; + } + + /** + * Creates the ContentValues for a given location object. + * + * @param location a given location + * @param trackId the id of the track it belongs to + * @return a filled in ContentValues object + */ + private static ContentValues createContentValues( + Location location, long trackId) { + ContentValues values = new ContentValues(); + values.put(TrackPointsColumns.TRACKID, trackId); + values.put(TrackPointsColumns.LATITUDE, + (int) (location.getLatitude() * 1E6)); + values.put(TrackPointsColumns.LONGITUDE, + (int) (location.getLongitude() * 1E6)); + // This is an ugly hack for Samsung phones that don't properly populate the + // time field. + values.put(TrackPointsColumns.TIME, + (location.getTime() == 0) + ? System.currentTimeMillis() + : location.getTime()); + if (location.hasAltitude()) { + values.put(TrackPointsColumns.ALTITUDE, location.getAltitude()); + } + if (location.hasBearing()) { + values.put(TrackPointsColumns.BEARING, location.getBearing()); + } + if (location.hasAccuracy()) { + values.put(TrackPointsColumns.ACCURACY, location.getAccuracy()); + } + if (location.hasSpeed()) { + values.put(TrackPointsColumns.SPEED, location.getSpeed()); + } + return values; + } + + /** + * Creates the ContentValues for a given Track object. + * + * Note: If the track has an id<0 the id column will not be filled. + * + * @param track a given track object + * @return a filled in ContentValues object + */ + private static ContentValues createContentValues(Track track) { + ContentValues values = new ContentValues(); + // Values id < 0 indicate no id is available: + if (track.getId() >= 0) { + values.put(TracksColumns._ID, track.getId()); + } + values.put(TracksColumns.NAME, track.getName()); + values.put(TracksColumns.DESCRIPTION, track.getDescription()); + values.put(TracksColumns.MAPID, track.getMapId()); + values.put(TracksColumns.CATEGORY, track.getCategory()); + values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints()); + values.put(TracksColumns.STARTID, track.getStartId()); + values.put(TracksColumns.STARTTIME, track.getStartTime()); + values.put(TracksColumns.STOPTIME, track.getStopTime()); + values.put(TracksColumns.STOPID, track.getStopId()); + values.put(TracksColumns.TOTALDISTANCE, track.getTotalDistance()); + values.put(TracksColumns.TOTALTIME, track.getTotalTime()); + values.put(TracksColumns.MOVINGTIME, track.getMovingTime()); + values.put(TracksColumns.MAXLAT, track.getTop()); + values.put(TracksColumns.MINLAT, track.getBottom()); + values.put(TracksColumns.MAXLON, track.getRight()); + values.put(TracksColumns.MINLON, track.getLeft()); + values.put(TracksColumns.AVGSPEED, track.getAverageSpeed()); + values.put(TracksColumns.AVGMOVINGSPEED, track.getAverageMovingSpeed()); + values.put(TracksColumns.MAXSPEED, track.getMaxSpeed()); + values.put(TracksColumns.MINELEVATION, track.getMinElevation()); + values.put(TracksColumns.MAXELEVATION, track.getMaxElevation()); + values.put(TracksColumns.ELEVATIONGAIN, track.getTotalElevationGain()); + values.put(TracksColumns.MINGRADE, track.getMinGrade()); + values.put(TracksColumns.MAXGRADE, track.getMaxGrade()); + return values; + } + + private static ContentValues createContentValues(Waypoint waypoint) { + ContentValues values = new ContentValues(); + // Values id < 0 indicate no id is available: + if (waypoint.getId() >= 0) { + values.put(WaypointsColumns._ID, waypoint.getId()); + } + values.put(WaypointsColumns.NAME, waypoint.getName()); + values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); + values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); + values.put(WaypointsColumns.ICON, waypoint.getIcon()); + values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); + values.put(WaypointsColumns.TYPE, waypoint.getType()); + values.put(WaypointsColumns.LENGTH, waypoint.getLength()); + values.put(WaypointsColumns.DURATION, waypoint.getDuration()); + values.put(WaypointsColumns.STARTTIME, waypoint.getStartTime()); + values.put(WaypointsColumns.STARTID, waypoint.getStartId()); + values.put(WaypointsColumns.STOPID, waypoint.getStopId()); + + values.put(WaypointsColumns.TOTALDISTANCE, waypoint.getTotalDistance()); + values.put(WaypointsColumns.TOTALTIME, waypoint.getTotalTime()); + values.put(WaypointsColumns.MOVINGTIME, waypoint.getMovingTime()); + values.put(WaypointsColumns.AVGSPEED, waypoint.getAverageSpeed()); + values.put(WaypointsColumns.AVGMOVINGSPEED, + waypoint.getAverageMovingSpeed()); + values.put(WaypointsColumns.MAXSPEED, waypoint.getMaxSpeed()); + values.put(WaypointsColumns.MINELEVATION, waypoint.getMinElevation()); + values.put(WaypointsColumns.MAXELEVATION, waypoint.getMaxElevation()); + values.put(WaypointsColumns.ELEVATIONGAIN, + waypoint.getTotalElevationGain()); + values.put(WaypointsColumns.MINGRADE, waypoint.getMinGrade()); + values.put(WaypointsColumns.MAXGRADE, waypoint.getMaxGrade()); + + Location location = waypoint.getLocation(); + if (location != null) { + values.put(WaypointsColumns.LATITUDE, + (int) (location.getLatitude() * 1E6)); + values.put(WaypointsColumns.LONGITUDE, + (int) (location.getLongitude() * 1E6)); + values.put(WaypointsColumns.TIME, location.getTime()); + if (location.hasAltitude()) { + values.put(WaypointsColumns.ALTITUDE, location.getAltitude()); + } + if (location.hasBearing()) { + values.put(WaypointsColumns.BEARING, location.getBearing()); + } + if (location.hasAccuracy()) { + values.put(WaypointsColumns.ACCURACY, location.getAccuracy()); + } + if (location.hasSpeed()) { + values.put(WaypointsColumns.SPEED, location.getSpeed()); + } + } + + return values; + } + + @Override + public Location createLocation(Cursor cursor) { + int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE); + int idxLongitude = + cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE); + int idxAltitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE); + int idxTime = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME); + int idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); + int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY); + int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED); + + Location location = new Location(""); + if (!cursor.isNull(idxLatitude)) { + location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6); + } + if (!cursor.isNull(idxLongitude)) { + location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6); + } + if (!cursor.isNull(idxAltitude)) { + location.setAltitude(cursor.getFloat(idxAltitude)); + } + if (!cursor.isNull(idxTime)) { + location.setTime(cursor.getLong(idxTime)); + } + if (!cursor.isNull(idxBearing)) { + location.setBearing(cursor.getFloat(idxBearing)); + } + if (!cursor.isNull(idxSpeed)) { + location.setSpeed(cursor.getFloat(idxSpeed)); + } + if (!cursor.isNull(idxAccuracy)) { + location.setAccuracy(cursor.getFloat(idxAccuracy)); + } + return location; + } + + /** + * Creates a Track object from a given cursor. + * + * @param cursor a cursor pointing at a db or provider with tracks + * @return a new Track object + */ + private static Track createTrack(Cursor cursor) { + int idxId = cursor.getColumnIndexOrThrow(TracksColumns._ID); + int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME); + int idxDescription = + cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION); + int idxMapId = cursor.getColumnIndexOrThrow(TracksColumns.MAPID); + int idxCategory = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY); + int idxStartId = cursor.getColumnIndexOrThrow(TracksColumns.STARTID); + int idxStartTime = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME); + int idxStopTime = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME); + int idxStopId = cursor.getColumnIndexOrThrow(TracksColumns.STOPID); + int idxNumPoints = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS); + int idxMaxlat = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT); + int idxMinlat = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT); + int idxMaxlon = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON); + int idxMinlon = cursor.getColumnIndexOrThrow(TracksColumns.MINLON); + + int idxTotalDistance = + cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE); + int idxTotalTime = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME); + int idxMovingTime = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME); + int idxAverageSpeed = cursor.getColumnIndexOrThrow(TracksColumns.AVGSPEED); + int idxAverageMovingSpeed = + cursor.getColumnIndexOrThrow(TracksColumns.AVGMOVINGSPEED); + int idxMaxSpeed = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED); + int idxMinElevation = + cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION); + int idxMaxElevation = + cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION); + int idxElevationGain = + cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN); + int idxMinGrade = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE); + int idxMaxGrade = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE); + + Track track = new Track(); + if (!cursor.isNull(idxId)) { + track.setId(cursor.getLong(idxId)); + } + if (!cursor.isNull(idxName)) { + track.setName(cursor.getString(idxName)); + } + if (!cursor.isNull(idxDescription)) { + track.setDescription(cursor.getString(idxDescription)); + } + if (!cursor.isNull(idxMapId)) { + track.setMapId(cursor.getString(idxMapId)); + } + if (!cursor.isNull(idxCategory)) { + track.setCategory(cursor.getString(idxCategory)); + } + if (!cursor.isNull(idxStartId)) { + track.setStartId(cursor.getInt(idxStartId)); + } + if (!cursor.isNull(idxStartTime)) { + track.setStartTime(cursor.getLong(idxStartTime)); + } + if (!cursor.isNull(idxStopTime)) { + track.setStopTime(cursor.getLong(idxStopTime)); + } + if (!cursor.isNull(idxStopId)) { + track.setStopId(cursor.getInt(idxStopId)); + } + if (!cursor.isNull(idxNumPoints)) { + track.setNumberOfPoints(cursor.getInt(idxNumPoints)); + } + if (!cursor.isNull(idxTotalDistance)) { + track.setTotalDistance(cursor.getFloat(idxTotalDistance)); + } + if (!cursor.isNull(idxTotalTime)) { + track.setTotalTime(cursor.getLong(idxTotalTime)); + } + if (!cursor.isNull(idxMovingTime)) { + track.setMovingTime(cursor.getLong(idxMovingTime)); + } + if (!cursor.isNull(idxMaxlat) + && !cursor.isNull(idxMinlat) + && !cursor.isNull(idxMaxlon) + && !cursor.isNull(idxMinlon)) { + int top = cursor.getInt(idxMaxlat); + int bottom = cursor.getInt(idxMinlat); + int right = cursor.getInt(idxMaxlon); + int left = cursor.getInt(idxMinlon); + track.setBounds(left, top, right, bottom); + } + if (!cursor.isNull(idxAverageSpeed)) { + track.setAverageSpeed(cursor.getFloat(idxAverageSpeed)); + } + if (!cursor.isNull(idxAverageMovingSpeed)) { + track.setAverageMovingSpeed(cursor.getFloat(idxAverageMovingSpeed)); + } + if (!cursor.isNull(idxMaxSpeed)) { + track.setMaxSpeed(cursor.getFloat(idxMaxSpeed)); + } + if (!cursor.isNull(idxMinElevation)) { + track.setMinElevation(cursor.getFloat(idxMinElevation)); + } + if (!cursor.isNull(idxMaxElevation)) { + track.setMaxElevation(cursor.getFloat(idxMaxElevation)); + } + if (!cursor.isNull(idxElevationGain)) { + track.setTotalElevationGain(cursor.getFloat(idxElevationGain)); + } + if (!cursor.isNull(idxMinGrade)) { + track.setMinGrade(cursor.getFloat(idxMinGrade)); + } + if (!cursor.isNull(idxMaxGrade)) { + track.setMaxGrade(cursor.getFloat(idxMaxGrade)); + } + return track; + } + + @Override + public Waypoint createWaypoint(Cursor cursor) { + int idxId = cursor.getColumnIndexOrThrow(WaypointsColumns._ID); + int idxName = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME); + int idxDescription = + cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION); + int idxCategory = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY); + int idxIcon = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON); + int idxTrackId = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID); + int idxType = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE); + int idxLength = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH); + int idxDuration = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION); + int idxStartTime = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME); + int idxStartId = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID); + int idxStopId = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID); + + int idxTotalDistance = + cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE); + int idxTotalTime = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME); + int idxMovingTime = + cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME); + int idxAverageSpeed = + cursor.getColumnIndexOrThrow(WaypointsColumns.AVGSPEED); + int idxAverageMovingSpeed = + cursor.getColumnIndexOrThrow(WaypointsColumns.AVGMOVINGSPEED); + int idxMaxSpeed = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED); + int idxMinElevation = + cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION); + int idxMaxElevation = + cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION); + int idxElevationGain = + cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN); + int idxMinGrade = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE); + int idxMaxGrade = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE); + + int idxLatitude = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE); + int idxLongitude = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE); + int idxAltitude = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE); + int idxTime = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME); + int idxBearing = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING); + int idxAccuracy = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY); + int idxSpeed = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED); + + Waypoint waypoint = new Waypoint(); + if (!cursor.isNull(idxId)) { + waypoint.setId(cursor.getLong(idxId)); + } + if (!cursor.isNull(idxName)) { + waypoint.setName(cursor.getString(idxName)); + } + if (!cursor.isNull(idxDescription)) { + waypoint.setDescription(cursor.getString(idxDescription)); + } + if (!cursor.isNull(idxCategory)) { + waypoint.setCategory(cursor.getString(idxCategory)); + } + if (!cursor.isNull(idxIcon)) { + waypoint.setIcon(cursor.getString(idxIcon)); + } + if (!cursor.isNull(idxTrackId)) { + waypoint.setTrackId(cursor.getLong(idxTrackId)); + } + if (!cursor.isNull(idxType)) { + waypoint.setType(cursor.getInt(idxType)); + } + if (!cursor.isNull(idxLength)) { + waypoint.setLength(cursor.getDouble(idxLength)); + } + if (!cursor.isNull(idxDuration)) { + waypoint.setDuration(cursor.getLong(idxDuration)); + } + if (!cursor.isNull(idxStartTime)) { + waypoint.setStartTime(cursor.getLong(idxStartTime)); + } + if (!cursor.isNull(idxStartId)) { + waypoint.setStartId(cursor.getLong(idxStartId)); + } + if (!cursor.isNull(idxStopId)) { + waypoint.setStopId(cursor.getLong(idxStopId)); + } + if (!cursor.isNull(idxTotalDistance)) { + waypoint.setTotalDistance(cursor.getFloat(idxTotalDistance)); + } + if (!cursor.isNull(idxTotalTime)) { + waypoint.setTotalTime(cursor.getLong(idxTotalTime)); + } + if (!cursor.isNull(idxMovingTime)) { + waypoint.setMovingTime(cursor.getLong(idxMovingTime)); + } + if (!cursor.isNull(idxAverageSpeed)) { + waypoint.setAverageSpeed(cursor.getFloat(idxAverageSpeed)); + } + if (!cursor.isNull(idxAverageMovingSpeed)) { + waypoint.setAverageMovingSpeed(cursor.getFloat(idxAverageMovingSpeed)); + } + if (!cursor.isNull(idxMaxSpeed)) { + waypoint.setMaxSpeed(cursor.getFloat(idxMaxSpeed)); + } + if (!cursor.isNull(idxMinElevation)) { + waypoint.setMinElevation(cursor.getFloat(idxMinElevation)); + } + if (!cursor.isNull(idxMaxElevation)) { + waypoint.setMaxElevation(cursor.getFloat(idxMaxElevation)); + } + if (!cursor.isNull(idxElevationGain)) { + waypoint.setTotalElevationGain(cursor.getFloat(idxElevationGain)); + } + if (!cursor.isNull(idxMinGrade)) { + waypoint.setMinGrade(cursor.getFloat(idxMinGrade)); + } + if (!cursor.isNull(idxMaxGrade)) { + waypoint.setMaxGrade(cursor.getFloat(idxMaxGrade)); + } + + Location location = new Location(""); + if (!cursor.isNull(idxLatitude) && !cursor.isNull(idxLongitude)) { + location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6); + location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6); + } + if (!cursor.isNull(idxAltitude)) { + location.setAltitude(cursor.getFloat(idxAltitude)); + } + if (!cursor.isNull(idxTime)) { + location.setTime(cursor.getLong(idxTime)); + } + if (!cursor.isNull(idxBearing)) { + location.setBearing(cursor.getFloat(idxBearing)); + } + if (!cursor.isNull(idxSpeed)) { + location.setSpeed(cursor.getFloat(idxSpeed)); + } + if (!cursor.isNull(idxAccuracy)) { + location.setAccuracy(cursor.getFloat(idxAccuracy)); + } + waypoint.setLocation(location); + return waypoint; + } + + @Override + public void deleteAllTracks() { + context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null); + context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI, + null, null); + context.getContentResolver().delete( + WaypointsColumns.CONTENT_URI, null, null); + } + + @Override + public void deleteTrack(long trackId) { + Track track = getTrack(trackId); + if (track != null) { + context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI, + "_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(), + null); + } + context.getContentResolver().delete(WaypointsColumns.CONTENT_URI, + WaypointsColumns.TRACKID + "=" + trackId, null); + context.getContentResolver().delete( + TracksColumns.CONTENT_URI, "_id=" + trackId, null); + } + + @Override + public void deleteWaypoint(long waypointId, + DescriptionGenerator descriptionGenerator) { + final Waypoint deletedWaypoint = getWaypoint(waypointId); + if (deletedWaypoint != null + && deletedWaypoint.getType() == Waypoint.TYPE_STATISTICS) { + final Waypoint nextWaypoint = + getNextStatisticsWaypointAfter(deletedWaypoint); + if (nextWaypoint != null) { + Log.d(MyTracksProvider.TAG, "Correcting marker " + nextWaypoint.getId() + + " after deleted marker " + deletedWaypoint.getId()); + nextWaypoint.combine(deletedWaypoint); + nextWaypoint.setDescription( + descriptionGenerator.generateWaypointDescription(nextWaypoint)); + if (!updateWaypoint(nextWaypoint)) { + Log.w(MyTracksProvider.TAG, "Update of marker was unsuccessful."); + } + } else { + Log.d(MyTracksProvider.TAG, + "No statistics marker after the deleted one was found."); + } + } + context.getContentResolver().delete( + WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null); + } + + @Override + public Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) { + final String selection = WaypointsColumns._ID + ">" + waypoint.getId() + + " AND " + WaypointsColumns.TRACKID + "=" + waypoint.getTrackId() + + " AND " + WaypointsColumns.TYPE + "=" + Waypoint.TYPE_STATISTICS; + final String sortOrder = WaypointsColumns._ID + " LIMIT 1"; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, + null /*projection*/, + selection, + null /*selectionArgs*/, + sortOrder); + if (cursor != null && cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + @Override + public boolean updateWaypoint(Waypoint waypoint) { + try { + final int rows = context.getContentResolver().update( + WaypointsColumns.CONTENT_URI, + createContentValues(waypoint), + "_id=" + waypoint.getId(), + null /*selectionArgs*/); + return rows == 1; + } catch (RuntimeException e) { + Log.e(MyTracksProvider.TAG, "Caught unexpected exception.", e); + } + return false; + } + + /** + * Finds a locations from the provider by the given selection. + * + * @param select a selection argument that identifies a unique location + * @return the fist location matching, or null if not found + */ + private Location findLocationBy(String select) { + Cursor cursor = null; + try { + cursor = context.getContentResolver().query( + TrackPointsColumns.CONTENT_URI, null, select, null, null); + if (cursor != null && cursor.moveToNext()) { + return createLocation(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpeceted exception.", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * Finds a track from the provider by the given selection. + * + * @param select a selection argument that identifies a unique track + * @return the first track matching, or null if not found + */ + private Track findTrackBy(String select) { + Cursor cursor = null; + try { + cursor = context.getContentResolver().query( + TracksColumns.CONTENT_URI, null, select, null, null); + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + @Override + public Location getLastLocation() { + return findLocationBy("_id=(select max(_id) from trackpoints)"); + } + + @Override + public Waypoint getFirstWaypoint(long trackId) { + Cursor cursor = context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, + null /*projection*/, + "trackid=" + trackId, + null /*selectionArgs*/, + "_id LIMIT 1"); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + cursor.close(); + } + } + return null; + } + + @Override + public Waypoint getWaypoint(long waypointId) { + Cursor cursor = context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, + null /*projection*/, + "_id=" + waypointId, + null /*selectionArgs*/, + null /*sortOrder*/); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return createWaypoint(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + cursor.close(); + } + } + return null; + } + + @Override + public long getLastLocationId(long trackId) { + final String[] projection = {"_id"}; + Cursor cursor = context.getContentResolver().query( + TrackPointsColumns.CONTENT_URI, + projection, + "_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")", + null /*selectionArgs*/, + null /*sortOrder*/); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getLong( + cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + cursor.close(); + } + } + return -1; + } + + @Override + public long getFirstWaypointId(long trackId) { + final String[] projection = {"_id"}; + Cursor cursor = context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, + projection, + "trackid=" + trackId, + null /*selectionArgs*/, + "_id LIMIT 1" /*sortOrder*/); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getLong( + cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + cursor.close(); + } + } + return -1; + } + + @Override + public long getLastWaypointId(long trackId) { + final String[] projection = {"_id"}; + Cursor cursor = context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, + projection, + WaypointsColumns.TRACKID + "=" + trackId, + null /*selectionArgs*/, + "_id DESC LIMIT 1" /*sortOrder*/); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getLong( + cursor.getColumnIndexOrThrow(WaypointsColumns._ID)); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + cursor.close(); + } + } + return -1; + } + + @Override + public Track getLastTrack() { + Cursor cursor = null; + try { + cursor = context.getContentResolver().query( + TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)", + null, null); + if (cursor != null && cursor.moveToNext()) { + return createTrack(cursor); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + @Override + public long getLastTrackId() { + String[] proj = { TracksColumns._ID }; + Cursor cursor = context.getContentResolver().query( + TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)", + null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getLong( + cursor.getColumnIndexOrThrow(TracksColumns._ID)); + } + } finally { + cursor.close(); + } + } + return -1; + } + + @Override + public Location getLocation(long id) { + String selection = TrackPointsColumns._ID + "=" + id; + return findLocationBy(selection); + } + + @Override + public Cursor getLocationsCursor(long trackId, long minTrackPointId, + int maxLocations, boolean descending) { + String selection; + if (minTrackPointId > 0) { + selection = String.format("%s=%d AND %s>=%d", + TrackPointsColumns.TRACKID, trackId, + TrackPointsColumns._ID, minTrackPointId); + } else { + selection = String.format("%s=%d", + TrackPointsColumns.TRACKID, trackId); + } + + String sortOrder = "_id " + (descending ? "DESC" : "ASC"); + if (maxLocations > 0) { + sortOrder += " LIMIT " + maxLocations; + } + + return context.getContentResolver().query( + TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder); + } + + @Override + public Cursor getWaypointsCursor(long trackId, long minWaypointId, + long maxWaypoints) { + String selection; + if (minWaypointId > 0) { + selection = String.format("%s=%d AND %s>=%d", + WaypointsColumns.TRACKID, trackId, + WaypointsColumns._ID, minWaypointId); + } else { + selection = String.format("%s=%d", + WaypointsColumns.TRACKID, trackId); + } + + String sortOrder = "_id ASC"; + if (maxWaypoints > 0) { + sortOrder += " LIMIT " + maxWaypoints; + } + + return context.getContentResolver().query( + WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder); + } + + @Override + public Track getTrack(long id) { + String select = TracksColumns._ID + "=" + id; + return findTrackBy(select); + } + + @Override + public long getTrackPoints(Track track, int maxPoints) { + long lastId = -1; + Cursor cursor = getLocationsCursor(track.getId(), -1, maxPoints, true); + if (cursor == null) { + Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!"); + return lastId; + } + try { + final int idColumnIdx = + cursor.getColumnIndexOrThrow(TrackPointsColumns._ID); + if (cursor.moveToLast()) { + do { + Location location = createLocation(cursor); + if (location == null) { + continue; + } + track.addLocation(location); + lastId = cursor.getLong(idColumnIdx); + } while (cursor.moveToPrevious()); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e); + } finally { + cursor.close(); + } + return lastId; + } + + @Override + public void getTrackPoints(Track track, TrackBuffer buffer) { + long startingPoint = buffer.getLastLocationRead() == 0 ? track.getStartId() + : buffer.getLastLocationRead(); + buffer.reset(); + Cursor cursor = getLocationsCursor(track.getId(), + startingPoint, + buffer.getSize(), false); + final int idColumnIdx = + cursor.getColumnIndexOrThrow(TrackPointsColumns._ID); + if (cursor == null) { + Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!"); + buffer.setInvalid(); + return; + } + try { + if (cursor.getCount() == 0) { + Log.w(MyTracksProvider.TAG, "No matching locations found."); + buffer.resetAt(startingPoint + buffer.getSize()); + return; + } + + if (!cursor.moveToFirst()) { + Log.w(MyTracksProvider.TAG, "Could not move to first."); + buffer.setInvalid(); + return; + } + + while (cursor.moveToNext()) { + Location location = createLocation(cursor); + if (location == null) { + continue; + } + buffer.add(location, cursor.getLong(idColumnIdx)); + } + if (buffer.getLocationsLoaded() == 0) { + Log.w(MyTracksProvider.TAG, "No locations read."); + buffer.resetAt(startingPoint + buffer.getSize()); + } + } catch (RuntimeException e) { + Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e); + } finally { + cursor.close(); + } + } + + @Override + public Cursor getTracksCursor(String selection) { + Cursor cursor = context.getContentResolver().query( + TracksColumns.CONTENT_URI, null, selection, null, "_id"); + return cursor; + } + + @Override + public Uri insertTrack(Track track) { + Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack"); + return context.getContentResolver().insert(TracksColumns.CONTENT_URI, + createContentValues(track)); + } + + @Override + public Uri insertTrackPoint(Location location, long trackId) { + Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint"); + return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI, + createContentValues(location, trackId)); + } + + @Override + public Uri insertWaypoint(Waypoint waypoint) { + Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint"); + waypoint.setId(-1); + return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI, + createContentValues(waypoint)); + } + + @Override + public Uri insertTrackAndTrackPoints(Track track) { + // Insert the track right away to have its ID + Uri trackUri = insertTrack(track); + long trackId = Long.parseLong(trackUri.getLastPathSegment()); + track.setId(trackId); + + // Insert all the points (associated with the track ID) + boolean firstPoint = true; + long pointId = -1; + for (Location location : track.getLocations()) { + Uri pointUri = insertTrackPoint(location, trackId); + pointId = Long.parseLong(pointUri.getLastPathSegment()); + if (firstPoint) { + track.setStartId(pointId); + firstPoint = false; + } + } + track.setStopId(pointId); + + // Update the track with the start and end point IDs + updateTrack(track); + return trackUri; + } + + @Override + public boolean trackExists(long id) { + Cursor cursor = null; + try { + final String[] projection = { TracksColumns._ID }; + cursor = context.getContentResolver().query( + TracksColumns.CONTENT_URI, + projection, + TracksColumns._ID + "=" + id/*selection*/, + null/*selectionArgs*/, + null/*sortOrder*/); + if (cursor != null && cursor.moveToNext()) { + return true; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return false; + } + + @Override + public void updateTrack(Track track) { + Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack"); + context.getContentResolver().update(TracksColumns.CONTENT_URI, + createContentValues(track), "_id=" + track.getId(), null); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/Track.java b/MyTracks/src/com/google/android/apps/mytracks/content/Track.java new file mode 100644 index 000000000..40a519911 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/Track.java @@ -0,0 +1,360 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; + +/** + * A class representing a (GPS) Track. + * + * @author Leif Hendrik Wilden + */ +public class Track implements Parcelable { + + /** + * Creator for a Track object + */ + public class Creator implements Parcelable.Creator { + + public Track createFromParcel(Parcel source) { + Track track = new Track(); + track.id = source.readLong(); + track.name = source.readString(); + track.description = source.readString(); + track.mapId = source.readString(); + track.category = source.readString(); + track.startId = source.readLong(); + track.stopId = source.readLong(); + track.startTime = source.readLong(); + track.stopTime = source.readLong(); + track.totalDistance = source.readDouble(); + track.totalTime = source.readLong(); + track.movingTime = source.readLong(); + track.averageSpeed = source.readDouble(); + track.averageMovingSpeed = source.readDouble(); + track.maxSpeed = source.readDouble(); + track.minElevation = source.readDouble(); + track.maxElevation = source.readDouble(); + track.totalElevationGain = source.readDouble(); + track.minGrade = source.readDouble(); + track.maxGrade = source.readDouble(); + left = source.readInt(); + top = source.readInt(); + right = source.readInt(); + bottom = source.readInt(); + track.numberOfPoints = source.readInt(); + for (int i = 0; i < track.numberOfPoints; ++i) { + Location loc = source.readParcelable(null); + track.locations.add(loc); + } + return track; + } + + public Track[] newArray(int size) { + return new Track[size]; + } + } + + public static final Creator CREATOR = new Track().new Creator(); + + /** + * The track points. + */ + private ArrayList locations = new ArrayList(); + + private long id = -1; + private String name = ""; + private String description = ""; + private String mapId = ""; + private long startId = -1; + private long stopId = -1; + private String category = ""; + + // Fields derived from locations. Use setDerivedFields() to set them: + //------------------------------------------------------------------- + + private long startTime = -1; + private long stopTime = -1; + private int numberOfPoints = 0; + private double totalDistance = 0; + private long totalTime = 0; + private long movingTime = 0; + private int left; + private int top; + private int right; + private int bottom; + + private double averageSpeed = 0; + private double averageMovingSpeed = 0; + private double maxSpeed = 0; + private double minElevation = 0; + private double maxElevation = 0; + private double totalElevationGain = 0; + private double minGrade = 0; + private double maxGrade = 0; + + public Track() { + left = Integer.MAX_VALUE; + top = Integer.MIN_VALUE; + right = Integer.MIN_VALUE; + bottom = Integer.MAX_VALUE; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeString(description); + dest.writeString(mapId); + dest.writeString(category); + dest.writeLong(startId); + dest.writeLong(stopId); + dest.writeLong(startTime); + dest.writeLong(stopTime); + dest.writeDouble(totalDistance); + dest.writeLong(totalTime); + dest.writeLong(movingTime); + dest.writeDouble(averageSpeed); + dest.writeDouble(averageMovingSpeed); + dest.writeDouble(maxSpeed); + dest.writeDouble(minElevation); + dest.writeDouble(maxElevation); + dest.writeDouble(totalElevationGain); + dest.writeDouble(minGrade); + dest.writeDouble(maxGrade); + dest.writeInt(left); + dest.writeInt(top); + dest.writeInt(right); + dest.writeInt(bottom); + dest.writeInt(numberOfPoints); + for (int i = 0; i < numberOfPoints; ++i) { + dest.writeParcelable(locations.get(i), 0); + } + } + + // Getters and setters: + //--------------------- + + public int getLeft() { + return left; + } + + public int getTop() { + return top; + } + + public int getRight() { + return right; + } + + public int getBottom() { + return bottom; + } + + public void setBounds(int aLeft, int aTop, int aRight, int aBottom) { + left = aLeft; + top = aTop; + right = aRight; + bottom = aBottom; + } + + public int describeContents() { + return 0; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public double getTotalDistance() { + return totalDistance; + } + + public void setTotalDistance(double totalDistance) { + this.totalDistance = totalDistance; + } + + public long getTotalTime() { + return totalTime; + } + + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } + + public long getMovingTime() { + return movingTime; + } + + public void setMovingTime(long movingTime) { + this.movingTime = movingTime; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getStopTime() { + return stopTime; + } + + public void setStopTime(long stopTime) { + this.stopTime = stopTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getStartId() { + return startId; + } + + public void setStartId(long startId) { + this.startId = startId; + } + + public long getStopId() { + return stopId; + } + + public void setStopId(long stopId) { + this.stopId = stopId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getMapId() { + return mapId; + } + + public void setMapId(String mapId) { + this.mapId = mapId; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public double getAverageSpeed() { + return averageSpeed; + } + + public void setAverageSpeed(double averageSpeed) { + this.averageSpeed = averageSpeed; + } + + public double getAverageMovingSpeed() { + return averageMovingSpeed; + } + + public void setAverageMovingSpeed(double averageMovingSpeed) { + this.averageMovingSpeed = averageMovingSpeed; + } + + public double getMaxSpeed() { + return maxSpeed; + } + + public void setMaxSpeed(double maxSpeed) { + this.maxSpeed = maxSpeed; + } + + public double getMinElevation() { + return minElevation; + } + + public void setMinElevation(double minElevation) { + this.minElevation = minElevation; + } + + public double getMaxElevation() { + return maxElevation; + } + + public void setMaxElevation(double maxElevation) { + this.maxElevation = maxElevation; + } + + public double getTotalElevationGain() { + return totalElevationGain; + } + + public void setTotalElevationGain(double totalElevationGain) { + this.totalElevationGain = totalElevationGain; + } + + public double getMinGrade() { + return minGrade; + } + + public void setMinGrade(double minGrade) { + this.minGrade = minGrade; + } + + public double getMaxGrade() { + return maxGrade; + } + + public void setMaxGrade(double maxGrade) { + this.maxGrade = maxGrade; + } + + public int getNumberOfPoints() { + return numberOfPoints; + } + + public void setNumberOfPoints(int numberOfPoints) { + this.numberOfPoints = numberOfPoints; + } + + public void addLocation(Location l) { + locations.add(l); + } + + public ArrayList getLocations() { + return locations; + } + + public void setLocations(ArrayList locations) { + this.locations = locations; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java new file mode 100644 index 000000000..84efcab78 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackBuffer.java @@ -0,0 +1,133 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import com.google.android.apps.mytracks.util.MyTracksUtils; + +import android.location.Location; + +/** + * A buffer of Locations from a track. + * This class contains some additional information about the loaded points. + * + * @author Sandor Dornbush + */ +public class TrackBuffer { + + /** + * An array of location objects that are part of a track. + */ + private final Location[] buffer; + + /** + * The id of the last location read from the track. + */ + private long lastLocationRead = 0; + + /** + * The number of locations loaded into the buffer. + */ + private int locationsLoaded = 0; + + /** + * Create a TrackBuffer with size elements. + * + * @param size The size of the buffer + */ + public TrackBuffer(int size) { + buffer = new Location[size]; + } + + /** + * Reset the track to a state with no locations. + */ + public void reset() { + resetAt(0); + } + + /** + * Reset the track at the given starting location id. + */ + public void resetAt(long lastLocation) { + lastLocationRead = lastLocation; + locationsLoaded = 0; + } + + public void setInvalid() { + lastLocationRead = Integer.MAX_VALUE; + } + + /** + * @return The number of locations that can be stored in this buffer + */ + public int getSize() { + return buffer.length; + } + + /** + * @param index The index of the location to fetch + * @return The location for the given index + */ + public Location get(int index) { + return buffer[index]; + } + + /** + * Adds a location to the end of the buffer. + * @param location The location to add + * @param id The id of the location to be added + */ + public void add(Location location, long id) { + buffer[locationsLoaded++] = location; + lastLocationRead = Math.max(lastLocationRead, id); + } + + /** + * @return The id of the last location loaded into the buffer + */ + public long getLastLocationRead() { + return lastLocationRead; + } + + /** + * @return The number of locations loaded into the buffer + */ + public int getLocationsLoaded() { + return locationsLoaded; + } + + /** + * Finds the start location, i.e. the one which is the first point of a + * segment with at least two points. + * + * @return the start location + */ + public Location findStartLocation() { + int numValidLocations = 0; + for (int i = 0; i < getLocationsLoaded(); i++) { + Location location = buffer[i]; + if (MyTracksUtils.isValidLocation(location)) { + numValidLocations++; + if (numValidLocations == 2) { + return buffer[i - 1]; + } + } else { + numValidLocations = 0; + } + } + return null; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackPointsColumns.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackPointsColumns.java new file mode 100644 index 000000000..f970e3014 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackPointsColumns.java @@ -0,0 +1,45 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Defines the URI for the track points provider and the available column names + * and content types. + * + * @author Leif Hendrik Wilden + */ +public interface TrackPointsColumns extends BaseColumns { + public static final Uri CONTENT_URI = + Uri.parse("content://com.google.android.maps.mytracks/trackpoints"); + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.google.trackpoint"; + public static final String CONTENT_ITEMTYPE = + "vnd.android.cursor.item/vnd.google.trackpoint"; + public static final String DEFAULT_SORT_ORDER = "_id"; + + /* All columns */ + public static final String TRACKID = "trackid"; + public static final String LATITUDE = "latitude"; + public static final String LONGITUDE = "longitude"; + public static final String ALTITUDE = "elevation"; + public static final String BEARING = "bearing"; + public static final String TIME = "time"; + public static final String ACCURACY = "accuracy"; + public static final String SPEED = "speed"; +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TracksColumns.java b/MyTracks/src/com/google/android/apps/mytracks/content/TracksColumns.java new file mode 100644 index 000000000..c461477c9 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/TracksColumns.java @@ -0,0 +1,62 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Defines the URI for the tracks provider and the available column names + * and content types. + * + * @author Leif Hendrik Wilden + */ +public interface TracksColumns extends BaseColumns { + + public static final Uri CONTENT_URI = + Uri.parse("content://com.google.android.maps.mytracks/tracks"); + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.google.track"; + public static final String CONTENT_ITEMTYPE = + "vnd.android.cursor.item/vnd.google.track"; + public static final String DEFAULT_SORT_ORDER = "_id"; + + /* All columns */ + public static final String NAME = "name"; + public static final String DESCRIPTION = "description"; + public static final String CATEGORY = "category"; + public static final String STARTID = "startid"; + public static final String STOPID = "stopid"; + public static final String STARTTIME = "starttime"; + public static final String STOPTIME = "stoptime"; + public static final String NUMPOINTS = "numpoints"; + public static final String TOTALDISTANCE = "totaldistance"; + public static final String TOTALTIME = "totaltime"; + public static final String MOVINGTIME = "movingtime"; + public static final String AVGSPEED = "avgspeed"; + public static final String AVGMOVINGSPEED = "avgmovingspeed"; + public static final String MAXSPEED = "maxspeed"; + public static final String MINELEVATION = "minelevation"; + public static final String MAXELEVATION = "maxelevation"; + public static final String ELEVATIONGAIN = "elevationgain"; + public static final String MINGRADE = "mingrade"; + public static final String MAXGRADE = "maxgrade"; + public static final String MINLAT = "minlat"; + public static final String MAXLAT = "maxlat"; + public static final String MINLON = "minlon"; + public static final String MAXLON = "maxlon"; + public static final String MAPID = "mapid"; +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.aidl b/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.aidl new file mode 100644 index 000000000..fece6fd44 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.aidl @@ -0,0 +1,2 @@ +package com.google.android.apps.mytracks.content; +parcelable Waypoint; \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.java b/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.java new file mode 100644 index 000000000..7535e5130 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/Waypoint.java @@ -0,0 +1,358 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.location.Location; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A way point. It has a location, meta data such as name, description, + * category, and icon, plus it can store track statistics for a "sub-track". + * + * @author Leif Hendrik Wilden + */ +public final class Waypoint implements Parcelable { + + /** + * Creator for a Waypoint object + */ + public static class Creator implements Parcelable.Creator { + + public Waypoint createFromParcel(Parcel source) { + Waypoint waypoint = new Waypoint(); + waypoint.id = source.readLong(); + waypoint.name = source.readString(); + waypoint.description = source.readString(); + waypoint.category = source.readString(); + waypoint.icon = source.readString(); + waypoint.trackId = source.readLong(); + waypoint.type = source.readInt(); + waypoint.length = source.readDouble(); + waypoint.duration = source.readLong(); + waypoint.startTime = source.readLong(); + waypoint.startId = source.readLong(); + waypoint.stopId = source.readLong(); + byte hasLocation = source.readByte(); + if (hasLocation > 0) { + waypoint.location = source.readParcelable(null); + } + waypoint.totalDistance = source.readDouble(); + waypoint.totalTime = source.readLong(); + waypoint.movingTime = source.readLong(); + waypoint.averageSpeed = source.readDouble(); + waypoint.averageMovingSpeed = source.readDouble(); + waypoint.maxSpeed = source.readDouble(); + waypoint.minElevation = source.readDouble(); + waypoint.maxElevation = source.readDouble(); + waypoint.totalElevationGain = source.readDouble(); + waypoint.minGrade = source.readDouble(); + waypoint.maxGrade = source.readDouble(); + return waypoint; + } + + public Waypoint[] newArray(int size) { + return new Waypoint[size]; + } + } + + public static final Creator CREATOR = new Creator(); + + public static final int TYPE_WAYPOINT = 0; + public static final int TYPE_STATISTICS = 1; + + private long id = -1; + private String name = ""; + private String description = ""; + private String category = ""; + private String icon = ""; + private long trackId = -1; + private int type = 0; + + private Location location; + + /** Length along the track in meters. */ + private double length = 0; + /** Time along the track in seconds. */ + private long duration = 0; + /** Start time of a segment */ + private long startTime = -1; + /** Start track point id */ + private long startId = -1; + /** Stop track point id */ + private long stopId = -1; + + private double totalDistance = 0; + private long totalTime = 0; + private long movingTime = 0; + private double averageSpeed = 0; + private double averageMovingSpeed = 0; + private double maxSpeed = 0; + private double minElevation = 0; + private double maxElevation = 0; + private double totalElevationGain = 0; + private double minGrade = 0; + private double maxGrade = 0; + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(name); + dest.writeString(description); + dest.writeString(category); + dest.writeString(icon); + dest.writeLong(trackId); + dest.writeInt(type); + dest.writeDouble(length); + dest.writeLong(duration); + dest.writeLong(startTime); + dest.writeLong(startId); + dest.writeLong(stopId); + dest.writeByte(location == null ? (byte) 0 : (byte) 1); + if (location != null) { + dest.writeParcelable(location, 0); + } + dest.writeDouble(totalDistance); + dest.writeLong(totalTime); + dest.writeLong(movingTime); + dest.writeDouble(averageSpeed); + dest.writeDouble(averageMovingSpeed); + dest.writeDouble(maxSpeed); + dest.writeDouble(minElevation); + dest.writeDouble(maxElevation); + dest.writeDouble(totalElevationGain); + dest.writeDouble(minGrade); + dest.writeDouble(maxGrade); + } + + /** + * Combines the statistics from another (earlier) waypoint with this one. + * This is needed when the user deletes a statistic waypoint. + * + * @param other the other waypoint + */ + public void combine(Waypoint other) { + totalTime += other.totalTime; + movingTime += other.movingTime; + totalDistance += other.totalDistance; + totalElevationGain += other.totalElevationGain; + averageSpeed = totalDistance / totalTime; + averageMovingSpeed = totalDistance / movingTime; + maxSpeed = Math.max(maxSpeed, other.maxSpeed); + minElevation = Math.min(minElevation, other.minElevation); + maxElevation = Math.max(maxElevation, other.maxElevation); + minGrade = Math.min(minGrade, other.minGrade); + maxGrade = Math.max(maxGrade, other.maxGrade); + } + + // Getters and setters: + //--------------------- + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Location getLocation() { + return location; + } + + public void setTrackId(long trackId) { + this.trackId = trackId; + } + + public int describeContents() { + return 0; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public double getTotalDistance() { + return totalDistance; + } + + public void setTotalDistance(double totalDistance) { + this.totalDistance = totalDistance; + } + + public long getTotalTime() { + return totalTime; + } + + public void setTotalTime(long totalTime) { + this.totalTime = totalTime; + } + + public long getMovingTime() { + return movingTime; + } + + public void setMovingTime(long movingTime) { + this.movingTime = movingTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getTrackId() { + return trackId; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public double getLength() { + return length; + } + + public void setLength(double length) { + this.length = length; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getStartId() { + return startId; + } + + public void setStartId(long startId) { + this.startId = startId; + } + + public long getStopId() { + return stopId; + } + + public void setStopId(long stopId) { + this.stopId = stopId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public double getAverageSpeed() { + return averageSpeed; + } + + public void setAverageSpeed(double averageSpeed) { + this.averageSpeed = averageSpeed; + } + + public double getAverageMovingSpeed() { + return averageMovingSpeed; + } + + public void setAverageMovingSpeed(double averageMovingSpeed) { + this.averageMovingSpeed = averageMovingSpeed; + } + + public double getMaxSpeed() { + return maxSpeed; + } + + public void setMaxSpeed(double maxSpeed) { + this.maxSpeed = maxSpeed; + } + + public double getMinElevation() { + return minElevation; + } + + public void setMinElevation(double minElevation) { + this.minElevation = minElevation; + } + + public double getMaxElevation() { + return maxElevation; + } + + public void setMaxElevation(double maxElevation) { + this.maxElevation = maxElevation; + } + + public double getTotalElevationGain() { + return totalElevationGain; + } + + public void setTotalElevationGain(double totalElevationGain) { + this.totalElevationGain = totalElevationGain; + } + + public double getMinGrade() { + return minGrade; + } + + public void setMinGrade(double minGrade) { + this.minGrade = minGrade; + } + + public double getMaxGrade() { + return maxGrade; + } + + public void setMaxGrade(double maxGrade) { + this.maxGrade = maxGrade; + } + + public void setLocation(Location location) { + this.location = location; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/WaypointsColumns.java b/MyTracks/src/com/google/android/apps/mytracks/content/WaypointsColumns.java new file mode 100644 index 000000000..8d60c36f5 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/content/WaypointsColumns.java @@ -0,0 +1,69 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.content; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * Defines the URI for the tracks provider and the available column names + * and content types. + * + * @author Leif Hendrik Wilden + */ +public interface WaypointsColumns extends BaseColumns { + + public static final Uri CONTENT_URI = + Uri.parse("content://com.google.android.maps.mytracks/waypoints"); + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.google.waypoint"; + public static final String CONTENT_ITEMTYPE = + "vnd.android.cursor.item/vnd.google.waypoint"; + public static final String DEFAULT_SORT_ORDER = "_id"; + + /* All columns */ + public static final String NAME = "name"; + public static final String DESCRIPTION = "description"; + public static final String CATEGORY = "category"; + public static final String ICON = "icon"; + public static final String TRACKID = "trackid"; + public static final String TYPE = "type"; + public static final String LENGTH = "length"; + public static final String DURATION = "duration"; + public static final String STARTTIME = "starttime"; + public static final String STARTID = "startid"; + public static final String STOPID = "stopid"; + + public static final String LATITUDE = "latitude"; + public static final String LONGITUDE = "longitude"; + public static final String ALTITUDE = "elevation"; + public static final String BEARING = "bearing"; + public static final String TIME = "time"; + public static final String ACCURACY = "accuracy"; + public static final String SPEED = "speed"; + + public static final String TOTALDISTANCE = "totaldistance"; + public static final String TOTALTIME = "totaltime"; + public static final String MOVINGTIME = "movingtime"; + public static final String AVGSPEED = "avgspeed"; + public static final String AVGMOVINGSPEED = "avgmovingspeed"; + public static final String MAXSPEED = "maxspeed"; + public static final String MINELEVATION = "minelevation"; + public static final String MAXELEVATION = "maxelevation"; + public static final String ELEVATIONGAIN = "elevationgain"; + public static final String MINGRADE = "mingrade"; + public static final String MAXGRADE = "maxgrade"; +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java new file mode 100644 index 000000000..501c68f96 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java @@ -0,0 +1,54 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +import com.google.android.apps.mytracks.MyTracksConstants; +import com.google.android.apps.mytracks.content.Track; + +import android.location.Location; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Utility class for decimating tracks at a given level of precision. + * + * @author Leif Hendrik Wilden + */ +public class MyTracksUtils { + + /** + * Checks if a given location is a valid (i.e. physically possible) location + * on Earth. Note: The special separator locations (which have latitude = + * 100) will not qualify as valid. Neither will locations with lat=0 and lng=0 + * as these are most likely "bad" measurements which often cause trouble. + * + * @param location the location to test + * @return true if the location is a valid location + */ + public static boolean isValidLocation(Location location) { + return location != null && Math.abs(location.getLatitude()) <= 90 + && Math.abs(location.getLongitude()) <= 180; + } + + /** + * This is a utility class w/ only static memebers. + */ + private MyTracksUtils() { + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java new file mode 100644 index 000000000..99cb104b6 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java @@ -0,0 +1,34 @@ +/* + * 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.android.apps.mytracks.util; + +/** + * Unit conversion constants. + * + * @author Sandor Dornbush + */ +public abstract class UnitConversions { + + public static final double KM_TO_MI = 0.621371192; + public static final double M_TO_FT = 3.2808399; + public static final double MI_TO_M = 1609.344; + public static final double MI_TO_FEET = 5280.0; + public static final double KMH_TO_MPH = 1000 * M_TO_FT / MI_TO_FEET; + public static final double TO_RADIANS = Math.PI / 180.0; + + private UnitConversions() { + } +}