Synced with head. Resolved conflicts.

This commit is contained in:
leifhendrik
2011-01-11 13:30:03 -08:00
82 changed files with 5323 additions and 1198 deletions
+1
View File
@@ -2,5 +2,6 @@ syntax:glob
*/bin
*/gen/**
*/proguard/**
.metadata
MyTracksTest/annotations.log
+5 -1
View File
@@ -114,6 +114,9 @@ limitations under the License.
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="com.dsi.ant.permission.ANT" />
<uses-permission android:name="com.dsi.ant.permission.ANT_ADMIN" />
<!-- Permissions for uploading tracks -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
@@ -156,7 +159,7 @@ limitations under the License.
<uses-feature android:name="android.hardware.location" android:required="true"/>
<uses-feature android:name="android.hardware.location.gps" android:required="true"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="true"/>
<!-- Features optional -->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
@@ -167,6 +170,7 @@ limitations under the License.
<uses-sdk android:minSdkVersion="3"/>
<supports-screens
android:xlargeScreens="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
+2 -1
View File
@@ -10,5 +10,6 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=Google Inc.:Google APIs:8
target=Google Inc.:Google APIs:9
proguard.config=proguard.cfg
android.library.reference.1=../MyTracksLib/
+10
View File
@@ -1,3 +1,4 @@
-target 6
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
@@ -5,9 +6,18 @@
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# Add the libraries, but ignore warnings about them
-libraryjars libs
-libraryjars ../MyTracksLib/libs
-dontwarn com.google.android.googleapps.**
-dontwarn com.android.common.**
-dontwarn com.google.android.gsf.**
# Keep everything android references from XMLs
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.app,backup.BackupAgent
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class com.android.vending.licensing.ILicensingService
+28 -8
View File
@@ -41,8 +41,7 @@ limitations under the License.
</LinearLayout>
</TableRow>
<View
android:layout_height="2dip" />
<View android:layout_height="2dip" />
<TableRow>
<TextView
@@ -58,6 +57,11 @@ limitations under the License.
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/about_bart"
android:textSize="20sp" />
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
@@ -81,8 +85,7 @@ limitations under the License.
</LinearLayout>
</TableRow>
<View
android:layout_height="2dip" />
<View android:layout_height="2dip" />
<TableRow>
<TextView
@@ -101,8 +104,7 @@ limitations under the License.
android:textSize="20sp" />
</TableRow>
<View
android:layout_height="2dip" />
<View android:layout_height="2dip" />
<TableRow>
<TextView
@@ -121,7 +123,25 @@ limitations under the License.
android:textSize="18sp" />
</TableRow>
<View
android:layout_height="4dip" />
<View android:layout_height="2dip" />
<TableRow>
<TextView
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:layout_column="1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/about_privacy"
android:textSize="20sp" />
<TextView
android:autoLink="web"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/about_privacy_weburl"
android:textSize="18sp" />
</TableRow>
<View android:layout_height="4dip" />
</TableLayout>
@@ -30,13 +30,15 @@ limitations under the License.
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip"
android:layout_weight="0">
<ZoomControls android:id="@+id/elevation_zoom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"/>
android:layout_centerHorizontal="true"
android:layout_alignParentRight="true"/>
</RelativeLayout>
<LinearLayout android:id="@+id/elevation_busypane"
android:orientation="vertical"
+16 -14
View File
@@ -17,7 +17,7 @@ limitations under the License.
<resources>
<!-- Task frequency -->
<string-array name="task_frequency_options">
<item>Off</item>
<item>Aus</item>
<item>1 min</item>
<item>2 min</item>
<item>5 min</item>
@@ -70,8 +70,8 @@ limitations under the License.
<!-- Minimum recording interval -->
<string-array name="min_recording_interval_options">
<item>batterie sparend (adaptiv)</item>
<item>hohe genauigkeit (adaptiv)</item>
<item>batteriesparend (adaptiv)</item>
<item>hohe Genauigkeit (adaptiv)</item>
<item>maximal (empfohlen)</item>
<item>2 s</item>
<item>3 s</item>
@@ -133,12 +133,12 @@ limitations under the License.
<string-array name="split_frequency_options_ft">
<item>Nicht zerteilen</item>
<item>100 meilen</item>
<item>50 meilen</item>
<item>25 meilen</item>
<item>10 meilen</item>
<item>5 meilen</item>
<item>1 meile</item>
<item>100 Meilen</item>
<item>50 Meilen</item>
<item>25 Meilen</item>
<item>10 Meilen</item>
<item>5 Meilen</item>
<item>1 Meile</item>
<item>60 min</item>
<item>30 min</item>
<item>15 min</item>
@@ -159,7 +159,7 @@ limitations under the License.
<item>Laufbahn</item>
<item>Geländelauf</item>
<item>Gehen</item>
<item>Schnelles Gehen</item>
<item>Gehen (Sport)</item>
<item>Wandern</item>
<item>Bergwandern</item>
<item>Bergsteigen</item>
@@ -181,11 +181,11 @@ limitations under the License.
<item>Eissegeln</item>
<item>Segeln</item>
<item>Kanadierfahren</item>
<item>Kajakfahren</item>
<item>Paddeln</item>
<item>Kajak fahren</item>
<item>Kanuwandern</item>
<item>Rudern</item>
<item>Motorbootfahren</item>
<item>Motorboot fahren</item>
<item>Fähre</item>
<item>Ferngesteuertes Boot</item>
<item>Surfen</item>
@@ -229,13 +229,14 @@ limitations under the License.
<item>Bank</item>
<item>Kneipe</item>
<item>Grill</item>
<item>Imbiss</item>
<item>Gebäude</item>
<item>Bushaltestelle</item>
<item>Straßenbahnhaltestelle</item>
<item>Bahnhof</item>
<item>S-Bahnhof</item>
<item>Café</item>
<item>Lagerfeuer</item>
<item>Feuerstelle</item>
<item>Campingplatz</item>
<item>Zeltplatz</item>
<item>Kirche</item>
@@ -278,6 +279,7 @@ limitations under the License.
<item>Tunnel</item>
<item>Baum</item>
<item>Wasserfall</item>
<item>Springbrunnen</item>
<item>Wegpunkt</item>
</array>
+161 -71
View File
@@ -26,51 +26,54 @@ limitations under the License.
<string name="delete_all_tracks">Alle Tracks löschen</string>
<string name="settings">Einstellungen</string>
<string name="elevation">Höhe</string>
<string name="choose_track_message">Bitte wählen Sie einen Track zur Ansicht aus</string>
<string name="choose_track_message">Bitte wähle einen Track zur Ansicht aus</string>
<string name="current_track">Aktueller Track</string>
<string name="current_segment">Letztes Segment</string>
<string name="sensor_state">Sensorstatus</string>
<string name="aggregated_stats">Aggregierte Statistiken</string>
<!-- Error messages -->
<string name="error_no_location_manager">Es wurde kein \'Location Manager\' auf diesem Gerät gefunden.</string>
<string name="error_no_gps_location_provider">Konnte keinen GPS-Empfänger auf diesem Gerät finden.</string>
<string name="error_sending_to_fusiontables">Beim Senden Ihres Tracks an Google Fusion Tables ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.</string>
<string name="error_sending_to_docs">Beim Senden Ihres Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal.</string>
<string name="error_unable_to_start_recording">Aufzeichnen fehlgeschlagen. Bitte versuchen Sie es nochmal.</string>
<string name="error_unable_to_stop_recording">Anhalten unmöglich. Bitte versuchen Sie es nochmal.</string>
<string name="error_unable_to_read_file">Datei nicht lesbar.</string>
<string name="error_no_location_manager">Es wurde kein Dienst zur Positionsermittlung auf diesem Gerät gefunden.</string>
<string name="error_no_gps_location_provider">Es wurde kein GPS-Empfänger auf diesem Gerät gefunden.</string>
<string name="error_sending_to_fusiontables">Beim Senden des Tracks an Google Fusion Tables ist ein Fehler aufgetreten. Bitte versuche es später noch einmal.</string>
<string name="error_sending_to_docs">Beim Senden des Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuche es später noch einmal.</string>
<string name="error_unable_to_start_recording">Start der Aufzeichnung fehlgeschlagen. Bitte versuche es noch einmal.</string>
<string name="error_unable_to_stop_recording">Anhalten unmöglich. Bitte versuche es noch einmal.</string>
<string name="error_unable_to_read_file">Konnte Datei nicht lesen.</string>
<string name="error_invalid_gpx_format">Datei ist nicht im GPX-Format.</string>
<string name="error_generic">Ein Fehler ist aufgetreten. Bitte versuchen Sie es nochmal.</string>
<string name="error_generic">Ein Fehler ist aufgetreten. Bitte versuche es noch einmal.</string>
<string name="error_track_does_not_exist">Track existiert nicht.</string>
<string name="error_out_of_memory">Die Datei ist zu groß (Obergrenze sind 500 KB). Operation abgebrochen.</string>
<string name="error_out_of_memory">Die Datei ist zu groß (maximal 500 KB). Operation abgebrochen.</string>
<string name="error_unable_to_insert_marker">Der Wegpunkt kann nicht einfügt werden. Keine Positionsbestimmung möglich.</string>
<!-- Status messages -->
<string name="wait_for_fix">Warten auf ein besseres GPS-Signal. Bitte stellen Sie sicher, dass der Himmel sichtbar ist.</string>
<string name="wait_for_fix">Warte auf ein besseres GPS-Signal. Bitte stelle sicher, dass der Himmel sichtbar ist.</string>
<string name="status_now_recording">Ein neuer Track wird aufgezeichnet.</string>
<string name="status_stopped_recording">Die Aufzeichnung wurde beendet.</string>
<string name="status_new_fusiontable_has_been_created">Eine neue Tabelle wurde auf google.com/fusiontables erstellt.</string>
<string name="status_tracks_have_been_uploaded">Ihr Track wurde auf maps.google.com gespeichert.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Ihr Track wurde in einer neuen Tabelle bei docs.google.com gespeichert.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Ihr Track wurde an docs.google.com gesendet.</string>
<string name="status_enable_gps">Berühren Sie hier um zu den Sicherheits- und Positionseinstellungen zu gelangen. Aktiviere \'GPS-Satelliten\'. Drücken Sie dann den Zurück-Knopf um zurückzukehren.</string>
<string name="status_approximate_location">Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Funktürmen und WiFi-Netzwerken.</string>
<string name="status_no_location">Ihre Position kann derzeit nicht bestimmt werden. Aktivieren Sie mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stellen Sie sicher, dass der Himmel sichtbar ist.</string>
<string name="status_stopped_recording">Die Aufzeichnung wurde angehalten.</string>
<string name="status_new_fusiontable_has_been_created">Eine neue Karte wurde auf google.com/fusiontables erstellt.</string>
<string name="status_tracks_have_been_uploaded">Dein Track wurde auf maps.google.com gespeichert.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Dein Track wurde in einer neuen Tabelle bei docs.google.com gespeichert.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Dein Track wurde an docs.google.com gesendet.</string>
<string name="status_mymap_info">Manchmal dauert es ein paar Minuten, bis neue Tracks bei maps.google.com erscheinen.</string>
<string name="status_enable_gps">Hier berühren, um zu den Sicherheits- und Positionseinstellungen zu gelangen. Aktiviere \'GPS-Satelliten\'. Drücke dann den Zurück-Knopf, um hierher zurückzukehren.</string>
<string name="status_approximate_location">Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Mobilfunkstationen und WiFi-Netzwerken.</string>
<string name="status_no_location">Die Position kann derzeit nicht bestimmt werden. Aktiviere mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stelle sicher, dass der Himmel sichtbar ist.</string>
<string name="status_waypoint_inserted">Ein Wegpunkt wurde eingefügt.</string>
<string name="status_statistics_inserted">Ein Statistikwegpunkt wurde eingefügt.</string>
<!-- Progress dialog -->
<string name="progress_title">Bitte warten…</string>
<string name="progress_message">Ihr GPS-Track wird an Google gesendet.</string>
<string name="progress_message_reading_track">Lese Track</string>
<string name="progress_message_preparing_track">Bereite Track vor</string>
<string name="progress_message_authenticating_fusiontables">Anmelden bei Google Fusion Tables</string>
<string name="progress_message_authenticating_docs">Anmelden bei Google Docs</string>
<string name="progress_message_sending_docs">Track an Google Docs senden</string>
<string name="progress_message_sending_fusiontables">Track an Google Fusion Tables senden</string>
<string name="progress_message_creating_fusiontable">Neue \'Fusion Tabelle\' erstellen</string>
<string name="import_progress_message">Importiere GPS-Track. Das dauert ein wenig.</string>
<string name="write_progress_message">Schreibe Datei auf SD-Karte. Das dauert ein wenig.</string>
<string name="progress_message_updating_track">Aktualisiere Trackstatistik</string>
<string name="progress_message">Dein Track wird an Google gesendet...</string>
<string name="progress_message_reading_track">Lese Track...</string>
<string name="progress_message_preparing_track">Bereite Track vor...</string>
<string name="progress_message_authenticating_mymaps">Anmeldung bei Google Maps...</string>
<string name="progress_message_authenticating_docs">Anmeldung bei Google Docs...</string>
<string name="progress_message_sending_docs">Track wird an Google Docs gesendet...</string>
<string name="progress_message_sending_fusiontables">Track wird an Google Fusion Tables gesendet...</string>
<string name="progress_message_creating_fusiontable">Neue \'Fusion Tabelle\' wird erstellt...</string>
<string name="import_progress_message">Importiere GPS-Track. Das kann ein wenig dauern.</string>
<string name="write_progress_message">Schreibe Datei auf SD-Karte. Das kann ein wenig dauern.</string>
<string name="progress_message_updating_track">Aktualisiere Trackstatistik...</string>
<!-- Used by DeleteAllDataHandler -->
<string name="are_you_sure_question">Bist Du Dir wirklich sicher?</string>
@@ -79,16 +82,18 @@ limitations under the License.
<string name="marker_will_be_permanently_deleted">Dieser Wegpunkt wird endgültig von diesem Gerät gelöscht.</string>
<!-- Text and links when uploading to Google Fusion Tables -->
<string name="new_map_description">Erstellt von My Tracks für Android.</string>
<string name="fusiontable_link">Erstellt von &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; für Android.</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">Punkte</string>
<string name="recorded_date">Aufgezeichnet am</string>
<string name="category">"Aktivität"</string>
<string name="waypoint_category">"Art des Wegpunktes"</string>
<string name="part">"(Teil %d)"</string>
<string name="category">Aktivität</string>
<string name="waypoint_category">Art des Wegpunktes</string>
<string name="part">(Teil %d)</string>
<!-- Used by the track recording service -->
<string name="recording_your_track">"Ihr Track wird aufgezeichnet…"</string>
<string name="recording_your_track">Dein Track wird aufgezeichnet…</string>
<!-- Common -->
<string name="no">Nein</string>
@@ -99,9 +104,10 @@ limitations under the License.
<string name="cancel">Abbrechen</string>
<string name="edit">Bearbeiten</string>
<string name="save">Sichern</string>
<string name="close">Schließen</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Anfang)</string>
<string name="start">(Beginn)</string>
<string name="end">(Ende)</string>
<!-- TrackList -->
@@ -115,15 +121,22 @@ limitations under the License.
<string name="tracklist_write_to_sd">Auf SD-Karte speichern…</string>
<string name="tracklist_save_as_gpx">GPX-Datei exportieren</string>
<string name="tracklist_save_as_kml">KML-Datei exportieren</string>
<string name="tracklist_save_as_csv">CSV-Datei exportieren</string>
<string name="tracklist_save_as_tcx">TCX-Datei exportieren</string>
<string name="tracklist_btn_send_all_to_mymaps">An Google MyMaps senden</string>
<string name="tracklist_btn_delete_all">Alle Tracks löschen</string>
<string name="tracklist_pick_one">Eine Track auswählen. Lange berühren für mehr Optionen.</string>
<string name="tracklist_all_tracks">Alle Tracks:</string>
<!-- Export All -->
<string name="tracklist_btn_export_all">Alle Tracks exportieren</string>
<string name="export_done">Exportieren beendet</string>
<string name="tracklist_pick_one">Einen Track auswählen. Lange berühren für mehr Optionen.</string>
<string name="tracklist_all_tracks">Alle Tracks</string>
<string name="tracklist_this_track">Dieser Track</string>
<string name="tracklist_share_track">Mit Freunden teilen…</string>
<string name="tracklist_share_linkfusion">Ein Link zur Google Fusion Tables-Karte</string>
<string name="tracklist_share_gpx_file">Eine GPX-Datei</string>
<string name="tracklist_share_kml_file">Eine KML-Datei</string>
<string name="tracklist_share_csv_file">Eine CSV-Datei</string>
<string name="tracklist_share_tcx_file">Eine TCX-Datei</string>
<string name="tracklist_clear_map">Karte zurücksetzen.</string>
<!-- Waypoints list -->
@@ -151,43 +164,78 @@ limitations under the License.
<!-- MyTracks Settings -->
<string name="mytracks_settings">Einstellungen für My Tracks</string>
<string name="settings_display">Anzeige</string>
<string name="settings_recording">Aufzeichnen</string>
<string name="settings_sharing">Mit Freunden Teilen</string>
<string name="settings_recording">Aufzeichnung</string>
<string name="settings_sharing">Mit Freunden teilen</string>
<string name="settings_announcement_frequency">Dauer zwischen Ansagen:</string>
<string name="settings_announcement_frequency_summary">Kürzeste Zeit zwichen zwei Ansagen</string>
<string name="settings_not_available_summary">In dieser Android-Version nicht verfügbar</string>
<string name="settings_announcement_frequency">Dauer zwischen Ansagen</string>
<string name="settings_announcement_frequency_summary">Kürzeste Zeit zwischen zwei Ansagen</string>
<string name="settings_min_recording_distance">Kleinste Entfernung zwischen Punkten:</string>
<string name="settings_min_recording_distance">Kleinste Entfernung zwischen Punkten</string>
<string name="settings_min_recording_distance_summary">Kleinste Entfernung zwischen zwei Positionsaufzeichnungen</string>
<string name="settings_max_recording_distance">Neues Segment wenn Punkte weiter entfernt als:</string>
<string name="settings_max_recording_distance_summary">Der Track wird in Zwei geteilt wenn zwei folgende Punkte weiter entfernt sind als hier angegeben.</string>
<string name="settings_max_recording_distance">Neues Segment, wenn Punkte weiter entfernt als</string>
<string name="settings_max_recording_distance_summary">Der Track wird in zwei geteilt, wenn zwei folgende Punkte weiter entfernt sind als hier angegeben.</string>
<string name="settings_min_recording_interval">Aufzeichnungsfrequenz:</string>
<string name="settings_min_recording_interval_summary">Kürzeste Zeit zwischen zwei Positionsaufzeichnungen.</string>
<string name="settings_min_recording_interval">Aufzeichnungsfrequenz</string>
<string name="settings_min_recording_interval_summary">Kürzeste Zeit zwischen zwei Positionsaufzeichnungen</string>
<string name="settings_min_required_accuracy">Aufzeichnung pausieren wenn Genauigkeit kleiner als:</string>
<string name="settings_min_required_accuracy_summary">Die Aufzeichnung wird kurzfristig angehalten, wenn Ihre Position nicht genauer als hier angegeben ermittelt werden kann.</string>
<string name="settings_min_required_accuracy">Aufzeichnung pausieren, wenn Genauigkeit kleiner als</string>
<string name="settings_min_required_accuracy_summary">Die Aufzeichnung wird kurzfristig angehalten, wenn Deine Position nicht genauer als hier angegeben ermittelt werden kann.</string>
<string name="settings_metric_units">Entfernungen in metrischen Einheiten anzeigen</string>
<string name="settings_metric_units_summary_on">Alle Entfernungen werden in metrischen Einheiten angezeigt.</string>
<string name="settings_metric_units_summary_off">Alle Entfernungen werden in britischen Längeneinheiten angezeigt.</string>
<string name="settings_metric_units_summary_off">Alle Entfernungen werden in imperialen Längeneinheiten angezeigt.</string>
<string name="settings_map_public">Karten auf maps.google.com veröffentlichen</string>
<string name="settings_map_public_summary_on">Hochgeladene Karten auf maps.google.com sind für alle sichtbar.</string>
<string name="settings_map_public_summary_off">Hochgeladene Karten auf maps.google.com werden nicht öffentlich sichtbar aufgelistet.</string>
<string name="settings_report_speed_or_pace">Geschwindigkeitsanzeige:</string>
<string name="settings_report_speed_or_pace">Geschwindigkeitsanzeige</string>
<string name="settings_report_speed_or_pace_summary_on">Geschwindigkeit wird angezeigt.</string>
<string name="settings_report_speed_or_pace_summary_off">Gangart wird angezeigt.</string>
<string name="settings_report_speed_or_pace_summary_off">Pace wird angezeigt.</string>
<string name="settings_share_url_only">Nur URL als Einladung zum Teilen senden</string>
<string name="settings_share_url_only_summary_on">Beim teilen mit Freunden wird nur die Karten URL gesendet.</string>
<string name="settings_share_url_only_summary_off">Beim teilen mit Freunden wird der GPS Track selbst gesendet.</string>
<string name="settings_share_url_only_summary_on">Beim Teilen mit Freunden wird nur die Karten-URL gesendet.</string>
<string name="settings_share_url_only_summary_off">Beim Teilen mit Freunden wird der GPS-Track selbst gesendet.</string>
<string name="settings_split_frequency">Zerteilungs Frequenz</string>
<string name="settings_split_frequency_summary">Längste Dauer oder Entfernung nach der automatisch ein Marker eingefügt wird.</string>
<string name="pace_label">Tempo</string>
<string name="settings_split_frequency">Zerteilungsfrequenz</string>
<string name="settings_split_frequency_summary">Längste Dauer oder Entfernung, nach der automatisch ein Marker eingefügt wird.</string>
<string name="settings_auto_resume_track_timeout">Zeitbegrenzung der Auto-Wiederaufnahme</string>
<string name="settings_auto_resume_track_timeout_summary">Maximale Zeit in Minuten, nach der eine abgebrochene Aufzeichnung nach einem Telefonneustart wiederaufgenommen wird.</string>
<string name="settings_timestamp_track_name">Zeitstempel als Trackname</string>
<string name="settings_timestamp_track_name_summary_on">Der Trackname wird aus Datum und Zeit zum Aufzeichnungsbeginn zusammengesetzt.</string>
<string name="settings_timestamp_track_name_summary_off">Tracknamen erhalten eine fortlaufende Nummer.</string>
<string name="pace_label">Pace</string>
<string name="eclair_required">Wird nur unter Android 2.0 oder höher unterstützt</string>
<string name="settings_sensor_type">Sensortyp</string>
<string name="settings_sensor_type_summary">Der Typ des zu verbindenden Sensors</string>
<string name="settings_sensor_id">Sensor-Id</string>
<string name="settings_sensor_id_summary">Die zum Sensor zu sendende Id</string>
<string name="sensor_id_malformed">Die Id muss eine Ganzzahl sein.</string>
<string name="bluetooth_sensor_title">Bluetooth-Sensor</string>
<string name="bluetooth_sensor_summary">Der zu verbindende Bluetoothsensor</string>
<string name="settings_modular_stats_display_title">Art der Datenanzeige</string>
<string name="settings_backup">Datensicherung (BETA)</string>
<string name="settings_sd_backup">SD-Karten-Sicherung</string>
<string name="settings_backup_to_sd">Jetzt sichern</string>
<string name="settings_backup_to_sd_summary">Eine Kopie aller Deiner Tracks und Einstellungen auf die SD-Karte schreiben</string>
<string name="settings_restore_from_sd">Jetzt wiederherstellen</string>
<string name="settings_restore_from_sd_summary">Alle Tracks und Einstellungen von der SD-Karte holen.</string>
<string name="settings_no_backup_while_recording">Während ein Track aufgezeichnet wird, kann nicht mit der Datensicherung gearbeitet werden.</string>
<!-- Backups -->
<string name="restore_overwrites_warning">Eine Wiederherstellung von der SD-Karte ÜBERSCHREIBT alle vorhandenen Tracks und Einstellungen. Willst Du das wirklich?</string>
<string name="select_backup_to_restore">Wähle die wiederherzustellende Datensicherung aus</string>
<string name="no_backups">Keine Datensicherungen gefunden.</string>
<string name="backup_write_progress_message">Schreibe alle Tracks und Einstellungen auf die SD-Karte. Dies kann eine Weile dauern...</string>
<string name="backup_import_progress_message">Lese alle Tracks und Einstellungen von der SD-Karte. Dies kann eine Weile dauern...</string>
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS-Satelliten</string>
@@ -212,11 +260,11 @@ limitations under the License.
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_label">Durchschnitt in Bew.</string>
<string name="average_moving_pace_label">Durchschnitt in Bew.</string>
<string name="average_moving_pace_label">Pace in Bew.</string>
<!-- Average -->
<string name="average_speed_label">Durchschnittsgeschw.</string>
<string name="average_pace_label">Durchschnittstempo</string>
<string name="average_pace_label">Durchschnitts-Pace</string>
<string name="elevation_chart_name">Höhenprofil</string>
<string name="elevation_gain_label">Höhengewinn</string>
@@ -227,7 +275,7 @@ limitations under the License.
<!-- Min -->
<string name="min_elevation_label">Niedrigste Höhenlage</string>
<string name="min_grade_label">Minimale Steigung</string>
<string name="min_pace_label">Schnellstes Tempo</string>
<string name="min_pace_label">Minimaler Pace</string>
<!-- Max -->
<string name="max_elevation_label">Höchste Höhenlage</string>
@@ -237,14 +285,14 @@ limitations under the License.
<string name="loading_track">Lade Track…</string>
<!-- Chart Settings -->
<string name="chart_settings">Diagram Einstellungen</string>
<string name="chart_settings">Diagramm-Einstellungen</string>
<!-- X Axis -->
<string name="x_axis">X Achse</string>
<string name="x_axis">X-Achse</string>
<string name="by_distance">Nach Entfernung</string>
<string name="by_time">Nach Zeit</string>
<!-- Y Axis -->
<string name="y_axis">Y Achse</string>
<string name="y_axis">Y-Achse</string>
<!-- Units -->
<!-- Time -->
@@ -265,7 +313,7 @@ limitations under the License.
<string name="kilometer_per_hour_long">Kilometer pro Stunde</string>
<string name="per_kilometer">pro Kilometer</string>
<string name="mile_per_hour_long">Meilen pro Stunde</string>
<string name="per_mile">pro meile</string>
<string name="per_mile">pro Meile</string>
<!-- Stats Labels -->
<string name="moving_time_label">Zeit in Bewegung</string>
@@ -273,10 +321,14 @@ limitations under the License.
<string name="time_label">Zeit</string>
<string name="sd_unavail">SD-Karte nicht verfügbar</string>
<string name="speed">Geschwindigkeit</string>
<string name="start_trip">Sie müssen erst eine Strecke zurücklegen.</string>
<string name="start_trip">Du musst erst eine Aufzeichnung beginnen.</string>
<string name="statistics">Statistik</string>
<string name="total_distance_label">Gesamtentfernung</string>
<string name="distance_label">Entfernung</string>
<string name="power">Energie</string>
<string name="cadence">Kadenz</string>
<string name="heart_rate">Herzfrequenz</string>
<string name="max_heart_rate">Höchste Herzfrequenz</string>
<string name="waiting">Bitte warten…</string>
<!-- End User License Agreement -->
@@ -297,22 +349,24 @@ limitations under the License.
<!-- Getting the account to upload to. -->
<string name="no_account_found_title">Kein Konto vorhanden</string>
<string name="no_account_found">Kein Konto gefunden um Tracks zu versenden.</string>
<string name="choose_account_title">Wählen Sei ein Konto</string>
<string name="no_account_found">Kein Konto gefunden, um Tracks zu versenden.</string>
<string name="choose_account_title">Wähle ein Konto</string>
<!-- SD card -->
<string name="io_no_external_storage_found">Keine externes Speichermedium verfügbar. Stellen Sie sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird.</string>
<string name="io_no_external_storage_found">Keine externes Speichermedium verfügbar. Stelle sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird.</string>
<string name="io_write_failed">Die Datei kann nicht geschrieben werden.</string>
<string name="io_read_failed">Die Datei konnte nicht gelesen werden.</string>
<string name="io_create_dir_failed">Das Verzeichnis auf der SD-Karte kann nicht erstellt werden.</string>
<string name="io_write_finished">Der Track wurde auf die SD-Karte geschrieben.</string>
<string name="io_read_finished">Lesen von der SD-Karte beendet.</string>
<!-- VersionChecker -->
<string name="version_check_title">Suche nach neuer Version</string>
<string name="version_check_upgrade_button">Jetzt aktualisieren</string>
<string name="version_check_continue_button">Fortfahren ohne zu aktualisieren</string>
<string name="upgrade_required">Ihre Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisieren Sie %1$s.</string>
<string name="upgrade_recommended">Ihre Version von %1$s funktioniert möglicherweise nicht mehr richtig. Sie können sie jetzt aktualisieren, oder auf sie auf eigene Gefahr weiterverwenden. Einige Funktionen wie Datenaustausch mit Google-Diensten wird möglicherweise nicht mehr funktionieren, solange Sie die Software nicht aktualisieren.</string>
<string name="upgrade_available">Eine neue Version von %1$s ist jetzt verfügbar. Sie können Ihre aktuelle Version ohne Probleme weiterverwenden, aber um die neuesten Funktionen in Anspruch nehmen zu können, müssen sie die Software aktualisieren.</string>
<string name="version_check_continue_button">Fortfahren, ohne zu aktualisieren</string>
<string name="upgrade_required">Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisiere %1$s.</string>
<string name="upgrade_recommended">Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig. Du kannst sie jetzt aktualisieren, oder auf sie auf eigene Gefahr weiterverwenden. Einige Funktionen wie Datenaustausch mit Google-Diensten werden möglicherweise nicht mehr funktionieren, solange Du die Software nicht aktualisierst.</string>
<string name="upgrade_available">Eine neue Version von %1$s ist jetzt verfügbar. Du kannst Deine installierte Version ohne Probleme weiterverwenden, aber um die neuesten Funktionen in Anspruch nehmen zu können, musst Du die Software aktualisieren.</string>
<!-- Welcome -->
<string name="welcome">Willkommen</string>
@@ -329,4 +383,40 @@ limitations under the License.
$6 : Speed units (mph) : Pace units (per mile)
-->
<string name="announce_template">My Tracks %1$s %2$.2f %3$s in %4$s mit %5$s %6$s</string>
<!-- Check Units -->
<string name="check_units_title">Einheitenbenachrichtigung</string>
<string name="check_units_message">Voreingestellt sind metrische Einheiten. Willst Du diese Einstellung ändern?</string>
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
<string name="bluetooth_options">Bluetootheinstellungen</string>
<string name="bluetooth_pairing">Geräteverbindung</string>
<string name="bluetooth_pairing_summary">Öffnet Androids Bluetootheinstellungen, um Geräte zu verbinden.</string>
<string name="sensor_options">Sensoreinstellungen</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">Verbunden mit %s</string>
<string name="bluetooth_connecting">Verbinde mit Bluetoothsensor ...</string>
<string name="bluetooth_not_connected">Bluetoothsensor nicht verbunden.</string>
<!-- Sensor State -->
<string name="sensor_data_time">Sensor Data Time</string>
<string name="power_meter_enabled">Energiemessung aktiviert</string>
<string name="heart_rate_monitor_enabled">Herzfrequenzmessung aktiviert</string>
<string name="no_sensor_manager">Keine Sensoren verfügbar</string>
<string name="enabled">Aktiviert</string>
<string name="connecting">Verbinde...</string>
<string name="connected">Verbunden</string>
<string name="disconnected">Getrennt</string>
<string name="no_sensor_service">Kein Sensor-Dienst</string>
<string name="searching">Suche...</string>
<string name="sending">Sende...</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">Track-Aufzeichnungs-Benachrichtigungen empfangen</string>
<string name="read_tracks_permission_label">Aufgezeichnete Trackdaten lesen</string>
<string name="write_tracks_permission_label">Aufgezeichnete Trackdaten schreiben, Starten/Anhalten des Aufzeichnens von Tracks</string>
<string name="notification_permission_description">Werde benachrichtigt, wenn die Aufzeichnung eines Tracks beginnt oder anhält.</string>
<string name="read_tracks_permission_description">Ermöglicht das Lesen von Trackdaten wie Messpunkten oder Wegpunkten.</string>
<string name="write_tracks_permission_description">Ermöglicht das Schreiben von Trackdaten wie beim Starten der Aufzeichnung oder beim Hinzufügen von Wegpunkten.</string>
</resources>
+2
View File
@@ -343,6 +343,8 @@ limitations under the License.
<string name="about_version">Wersja:</string>
<string name="about_email">Wsparcie</string>
<string name="about_support">Wsparcie:</string>
<string name="about_bart">Bartłomiej Niechwiej</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">Wyślij do...</string>
+4
View File
@@ -393,12 +393,16 @@ limitations under the License.
<array name="sensor_type_options">
<item>None</item>
<item>ANT+ Sensors</item>
<item>SRM ANT+ Bridge</item>
<item>Zephyr Bluetooth</item>
</array>
<!-- DO NOT TRANSLATE -->
<array name="sensor_type_values">
<item>@string/none</item>
<item>@string/ant_sensor_type</item>
<item>@string/srm_ant_bridge_sensor_type</item>
<item>@string/zephyr_sensor_type</item>
</array>
+21 -7
View File
@@ -17,20 +17,34 @@ limitations under the License.
<resources>
<color name="black">#FF000000</color>
<color name="blue">#FF0000FF</color>
<color name="blue_transparent">#400000FF</color>
<color name="darkgray">#FF444444</color>
<color name="gold">#FFffd700</color>
<color name="gray">#FFAAAAAA</color>
<color name="green">#FF00AA00</color>
<color name="lightblue">#FF00FFFF</color>
<color name="opaque_shadow">#70000000</color>
<color name="purple">#FF401465</color>
<color name="purple_transparent">#40401465</color>
<color name="red">#FFFF0000</color>
<color name="red_transparent">#40FF0000</color>
<color name="stat_label_background">#FFF0F0F0</color>
<color name="stat_label_text">#FF0F0F0F</color>
<color name="white">#FFFFFFFF</color>
<color name="yellow">#FFF8FF00</color>
<color name="yellow_transparent">#40F8FF00</color>
<!-- Chart colors by series. -->
<!-- Green and a transparent green -->
<color name="elevation_border">#FF339933</color>
<color name="elevation_fill">#40B3CFB3</color>
<!-- Blue and a transparent blue -->
<color name="speed_border">#FF0066FF</color>
<color name="speed_fill">#40C8E0FF</color>
<!-- Red and a transparent red -->
<color name="heartrate_border">#FFCC3300</color>
<color name="heartrate_fill">#40E88A70</color>
<!-- Pink and a transparent pink -->
<color name="cadence_border">#FFF128F3</color>
<color name="cadence_fill">#40F128F3</color>
<!-- Purple and a transparent purple -->
<color name="power_border">#FF401465</color>
<color name="power_fill">#40401465</color>
</resources>
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<!-- DO NOT TRANSLATE ANY STRINGS IN THIS FILE. -->
<string name="app_name">My Tracks</string>
<!-- About Dialog -->
<string name="about_googlegroups">mytracks@googlegroups.com</string>
<string name="about_weburl">mytracks.appspot.com</string>
<string name="about_privacy_weburl">m.google.com/privacy</string>
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
<string name="about_bart">Bartlomiej Niechwiej</string>
<string name="about_dylan">Dylan Casey</string>
<string name="about_leif">Leif Hendrik Wilden</string>
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
<string name="about_sandor">Sandor Dornbush</string>
<!-- Sensor Types -->
<string name="none">NONE</string>
<string name="ant_sensor_type">ANT</string>
<string name="srm_ant_bridge_sensor_type">SRM_ANT_BRIDGE</string>
<string name="zephyr_sensor_type">ZEPHYR</string>
</resources>
+3
View File
@@ -17,6 +17,9 @@ limitations under the License.
<resources>
<!-- Keys for application preferences - DO NOT TRANSLATE -->
<string name="announcement_frequency_key">announcementFrequency</string>
<string name="ant_heart_rate_sensor_id_key">antHeartRateSensorId</string>
<string name="ant_power_sensor_id_key">antPowerSensorId</string>
<string name="ant_srm_bridge_sensor_id_key">antSRMBridgeSensorId</string>
<string name="auto_resume_track_timeout_key">autoResumeTrackTimeout</string>
<string name="bluetooth_device_key">bluetoothDevice</string>
<string name="bluetooth_pairing_key">bluetoothPairing</string>
+9 -24
View File
@@ -15,9 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- DO NOT TRANSLATE -->
<string name="app_name">My Tracks</string>
<!-- Used by the Menu -->
<string name="start_recording">Record track</string>
<string name="stop_recording">Stop recording</string>
@@ -105,6 +102,7 @@ limitations under the License.
<string name="error">Error</string>
<string name="success">Success</string>
<string name="waypoint">Waypoint</string>
<string name="license">License</string>
<string name="cancel">Cancel</string>
<string name="close">Close</string>
<string name="edit">Edit</string>
@@ -218,15 +216,15 @@ limitations under the License.
<string name="eclair_required">Only supported in Android 2.0 or above</string>
<string name="settings_sensor_type">Sensor Type</string>
<string name="settings_sensor_type_summary">The type of sensor to connect to.</string>
<string name="settings_sensor_id">Sensor Id</string>
<string name="settings_sensor_id_summary">The Id sent to sensors.</string>
<string name="sensor_id_malformed">The Id must be an integer value.</string>
<string name="settings_sensor_type_summary">The type of sensor to connect to</string>
<string name="settings_heart_rate_ant_sensor_id">Clear ANT heart rate sensor pairing</string>
<string name="settings_power_ant_sensor_id">Clear ANT power sensor pairing</string>
<string name="settings_srm_bridge_ant_sensor_id">Clear ANT srm bridge sensor pairing</string>
<string name="settings_ant_not_paired">ANT sensor not paired</string>
<string name="settings_ant_paired">ANT sensor paired with: %d</string>
<string name="bluetooth_sensor_title">Bluetooth sensor</string>
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to.</string>
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to</string>
<string name="settings_modular_stats_display_title">Modular Stats Display</string>
<string name="settings_modular_stats_display_summary_on">The stats screen is split in various modes.</string>
<string name="settings_modular_stats_display_summary_off">Display all stats on one screen</string>
<string name="settings_backup">Backup (BETA)</string>
<string name="settings_sd_backup">SD Card Backup</string>
@@ -363,17 +361,8 @@ limitations under the License.
<string name="help">Help</string>
<string name="about_team">Team:</string>
<string name="about_version">Version:</string>
<string name="about_email">Support</string>
<string name="about_support">Support:</string>
<!-- DO NOT TRANSLATE -->
<string name="about_googlegroups">mytracks@googlegroups.com</string>
<string name="about_weburl">mytracks.appspot.com</string>
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
<string name="about_dylan">Dylan Casey</string>
<string name="about_leif">Leif Hendrik Wilden</string>
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
<string name="about_sandor">Sandor Dornbush</string>
<string name="about_privacy">Privacy:</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">Send to...</string>
@@ -433,10 +422,6 @@ limitations under the License.
<string name="sensor_bluetooth_options_key">bluetoothSensorsEnabled</string>
<string name="sensor_options">Sensor Options</string>
<!-- Sensor Types => DO NOT TRANSLATE -->
<string name="none">NONE</string>
<string name="zephyr_sensor_type">ZEPHYR</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">Connected to %s</string>
<string name="bluetooth_connecting">Bluetooth sensor connecting ...</string>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<resources>
<!-- DO NOT TRANSLATE -->
<string name="waypoint_icon_url">http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png</string>
<string name="stats_icon_url">http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png</string>
</resources>
+12
View File
@@ -122,6 +122,18 @@ limitations under the License.
android:summary="@string/bluetooth_pairing_summary"
android:persistent="false" />
</PreferenceCategory>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_heart_rate_sensor_id_key"
android:title="@string/settings_heart_rate_ant_sensor_id"/>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_power_sensor_id_key"
android:title="@string/settings_power_ant_sensor_id"/>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_srm_bridge_sensor_id_key"
android:title="@string/settings_srm_bridge_ant_sensor_id"/>
</PreferenceScreen>
<!-- Sharing settings -->
+276
View File
@@ -0,0 +1,276 @@
/*
* Copyright 2010 Dynastream Innovations 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.dsi.ant;
/**
* The Android Ant API is not finalized, and *will* change. Use at your
* own risk.
*
* Public API for controlling the Ant Service.
* AntDefines contains definitions commonly used in ANT messaging.
*
* @hide
*/
public class AntDefine {
//////////////////////////////////////////////
// Library Version Information
//////////////////////////////////////////////
public static final int ANT_LIBRARY_VERSION_CODE = 5;
public static final int ANT_LIBRARY_VERSION_MAJOR = 1;
public static final int ANT_LIBRARY_VERSION_MINOR = 4;
public static final String ANT_LIBRARY_VERSION_NAME = String.valueOf(ANT_LIBRARY_VERSION_MAJOR) + "." + String.valueOf(ANT_LIBRARY_VERSION_MINOR);
//////////////////////////////////////////////
// Valid Configuration Values
//////////////////////////////////////////////
public static final byte MIN_BIN = 0;
public static final byte MAX_BIN = 10;
public static final short MIN_DEVICE_ID = 0;
public static final short MAX_DEVICE_ID = (short)65535;
public static final short MIN_BUFFER_THRESHOLD = 0;
public static final short MAX_BUFFER_THRESHOLD = 996;
//////////////////////////////////////////////
// ANT Message Payload Size
//////////////////////////////////////////////
public static final byte ANT_STANDARD_DATA_PAYLOAD_SIZE =((byte)8);
//////////////////////////////////////////////
// ANT LIBRARY Extended Data Message Fields
// NOTE: You must check the extended message
// bitfield first to find out which fields
// are present before accessing them!
//////////////////////////////////////////////
public static final byte ANT_EXT_MESG_DEVICE_ID_FIELD_SIZE =((byte)4);
//public static final byte ANT_EXT_STRING_SIZE =((byte)19); // this is the additional buffer space required required for setting USB Descriptor strings
public static final byte ANT_EXT_STRING_SIZE =((byte)0); // changed to 0 as we will not be dealing with ANT USB parts.
//////////////////////////////////////////////
// ANT Extended Data Message Bifield Definitions
//////////////////////////////////////////////
public static final byte ANT_EXT_MESG_BITFIELD_DEVICE_ID =((byte)0x80); // first field after bitfield
public static final byte ANT_EXT_MESG_BITFIELD_RSSI =((byte)0x40); // next field after ID, if there is one
public static final byte ANT_EXT_MESG_BITFIELD_TIME_STAMP =((byte)0x20); // next field after RSSI, if there is one
// 4 bits free reserved set to 0
public static final byte ANT_EXT_MESG_BIFIELD_EXTENSION =((byte)0x01);
// extended message input bitfield defines
public static final byte ANT_EXT_MESG_BITFIELD_OVERWRITE_SHARED_ADR =((byte)0x10);
public static final byte ANT_EXT_MESG_BITFIELD_TRANSMISSION_TYPE =((byte)0x08);
//////////////////////////////////////////////
// ID Definitions
//////////////////////////////////////////////
public static final byte ANT_ID_SIZE =((byte)4);
public static final byte ANT_ID_TRANS_TYPE_OFFSET =((byte)3);
public static final byte ANT_ID_DEVICE_TYPE_OFFSET =((byte)2);
public static final byte ANT_ID_DEVICE_NUMBER_HIGH_OFFSET =((byte)1);
public static final byte ANT_ID_DEVICE_NUMBER_LOW_OFFSET =((byte)0);
public static final byte ANT_ID_DEVICE_TYPE_PAIRING_FLAG =((byte)0x80);
public static final byte ANT_TRANS_TYPE_SHARED_ADDR_MASK =((byte)0x03);
public static final byte ANT_TRANS_TYPE_1_BYTE_SHARED_ADDRESS =((byte)0x02);
public static final byte ANT_TRANS_TYPE_2_BYTE_SHARED_ADDRESS =((byte)0x03);
//////////////////////////////////////////////
// Assign Channel Parameters
//////////////////////////////////////////////
public static final byte PARAMETER_RX_NOT_TX =((byte)0x00);
public static final byte PARAMETER_TX_NOT_RX =((byte)0x10);
public static final byte PARAMETER_SHARED_CHANNEL =((byte)0x20);
public static final byte PARAMETER_NO_TX_GUARD_BAND =((byte)0x40);
public static final byte PARAMETER_ALWAYS_RX_WILD_CARD_SEARCH_ID =((byte)0x40); //Pre-AP2
public static final byte PARAMETER_RX_ONLY =((byte)0x40);
//////////////////////////////////////////////
// Ext. Assign Channel Parameters
//////////////////////////////////////////////
public static final byte EXT_PARAM_ALWAYS_SEARCH =((byte)0x01);
public static final byte EXT_PARAM_FREQUENCY_AGILITY =((byte)0x04);
//////////////////////////////////////////////
// Radio TX Power Definitions
//////////////////////////////////////////////
public static final byte RADIO_TX_POWER_LVL_MASK =((byte)0x03);
public static final byte RADIO_TX_POWER_LVL_0 =((byte)0x00); //(formerly: RADIO_TX_POWER_MINUS20DB); lowest
public static final byte RADIO_TX_POWER_LVL_1 =((byte)0x01); //(formerly: RADIO_TX_POWER_MINUS10DB);
public static final byte RADIO_TX_POWER_LVL_2 =((byte)0x02); //(formerly: RADIO_TX_POWER_MINUS5DB);
public static final byte RADIO_TX_POWER_LVL_3 =((byte)0x03); //(formerly: RADIO_TX_POWER_0DB); highest
//////////////////////////////////////////////
// Channel Status
//////////////////////////////////////////////
public static final byte STATUS_CHANNEL_STATE_MASK =((byte)0x03);
public static final byte STATUS_UNASSIGNED_CHANNEL =((byte)0x00);
public static final byte STATUS_ASSIGNED_CHANNEL =((byte)0x01);
public static final byte STATUS_SEARCHING_CHANNEL =((byte)0x02);
public static final byte STATUS_TRACKING_CHANNEL =((byte)0x03);
//////////////////////////////////////////////
// Standard capabilities defines
//////////////////////////////////////////////
public static final byte CAPABILITIES_NO_RX_CHANNELS =((byte)0x01);
public static final byte CAPABILITIES_NO_TX_CHANNELS =((byte)0x02);
public static final byte CAPABILITIES_NO_RX_MESSAGES =((byte)0x04);
public static final byte CAPABILITIES_NO_TX_MESSAGES =((byte)0x08);
public static final byte CAPABILITIES_NO_ACKD_MESSAGES =((byte)0x10);
public static final byte CAPABILITIES_NO_BURST_TRANSFER =((byte)0x20);
//////////////////////////////////////////////
// Advanced capabilities defines
//////////////////////////////////////////////
public static final byte CAPABILITIES_OVERUN_UNDERRUN =((byte)0x01); // Support for this functionality has been dropped
public static final byte CAPABILITIES_NETWORK_ENABLED =((byte)0x02);
public static final byte CAPABILITIES_AP1_VERSION_2 =((byte)0x04); // This Version of the AP1 does not support transmit and only had a limited release
public static final byte CAPABILITIES_SERIAL_NUMBER_ENABLED =((byte)0x08);
public static final byte CAPABILITIES_PER_CHANNEL_TX_POWER_ENABLED =((byte)0x10);
public static final byte CAPABILITIES_LOW_PRIORITY_SEARCH_ENABLED =((byte)0x20);
public static final byte CAPABILITIES_SCRIPT_ENABLED =((byte)0x40);
public static final byte CAPABILITIES_SEARCH_LIST_ENABLED =((byte)0x80);
//////////////////////////////////////////////
// Advanced capabilities 2 defines
//////////////////////////////////////////////
public static final byte CAPABILITIES_LED_ENABLED =((byte)0x01);
public static final byte CAPABILITIES_EXT_MESSAGE_ENABLED =((byte)0x02);
public static final byte CAPABILITIES_SCAN_MODE_ENABLED =((byte)0x04);
public static final byte CAPABILITIES_RESERVED =((byte)0x08);
public static final byte CAPABILITIES_PROX_SEARCH_ENABLED =((byte)0x10);
public static final byte CAPABILITIES_EXT_ASSIGN_ENABLED =((byte)0x20);
public static final byte CAPABILITIES_FREE_1 =((byte)0x40);
public static final byte CAPABILITIES_FIT1_ENABLED =((byte)0x80);
//////////////////////////////////////////////
// Advanced capabilities 3 defines
//////////////////////////////////////////////
public static final byte CAPABILITIES_SENSRCORE_ENABLED =((byte)0x01);
public static final byte CAPABILITIES_RESERVED_1 =((byte)0x02);
public static final byte CAPABILITIES_RESERVED_2 =((byte)0x04);
public static final byte CAPABILITIES_RESERVED_3 =((byte)0x08);
//////////////////////////////////////////////
// Burst Message Sequence
//////////////////////////////////////////////
public static final byte CHANNEL_NUMBER_MASK =((byte)0x1F);
public static final byte SEQUENCE_NUMBER_MASK =((byte)0xE0);
public static final byte SEQUENCE_NUMBER_ROLLOVER =((byte)0x60);
public static final byte SEQUENCE_FIRST_MESSAGE =((byte)0x00);
public static final byte SEQUENCE_LAST_MESSAGE =((byte)0x80);
public static final byte SEQUENCE_NUMBER_INC =((byte)0x20);
//////////////////////////////////////////////
// Control Message Flags
//////////////////////////////////////////////
public static final byte BROADCAST_CONTROL_BYTE =((byte)0x00);
public static final byte ACKNOWLEDGED_CONTROL_BYTE =((byte)0xA0);
//////////////////////////////////////////////
// Response / Event Codes
//////////////////////////////////////////////
public static final byte RESPONSE_NO_ERROR =((byte)0x00);
public static final byte NO_EVENT =((byte)0x00);
public static final byte EVENT_RX_SEARCH_TIMEOUT =((byte)0x01);
public static final byte EVENT_RX_FAIL =((byte)0x02);
public static final byte EVENT_TX =((byte)0x03);
public static final byte EVENT_TRANSFER_RX_FAILED =((byte)0x04);
public static final byte EVENT_TRANSFER_TX_COMPLETED =((byte)0x05);
public static final byte EVENT_TRANSFER_TX_FAILED =((byte)0x06);
public static final byte EVENT_CHANNEL_CLOSED =((byte)0x07);
public static final byte EVENT_RX_FAIL_GO_TO_SEARCH =((byte)0x08);
public static final byte EVENT_CHANNEL_COLLISION =((byte)0x09);
public static final byte EVENT_TRANSFER_TX_START =((byte)0x0A); // a pending transmit transfer has begun
public static final byte EVENT_CHANNEL_ACTIVE =((byte)0x0F);
public static final byte EVENT_TRANSFER_TX_NEXT_MESSAGE =((byte)0x11); // only enabled in FIT1
public static final byte CHANNEL_IN_WRONG_STATE =((byte)0x15); // returned on attempt to perform an action from the wrong channel state
public static final byte CHANNEL_NOT_OPENED =((byte)0x16); // returned on attempt to communicate on a channel that is not open
public static final byte CHANNEL_ID_NOT_SET =((byte)0x18); // returned on attempt to open a channel without setting the channel ID
public static final byte CLOSE_ALL_CHANNELS =((byte)0x19); // returned when attempting to start scanning mode, when channels are still open
public static final byte TRANSFER_IN_PROGRESS =((byte)0x1F); // returned on attempt to communicate on a channel with a TX transfer in progress
public static final byte TRANSFER_SEQUENCE_NUMBER_ERROR =((byte)0x20); // returned when sequence number is out of order on a Burst transfer
public static final byte TRANSFER_IN_ERROR =((byte)0x21);
public static final byte TRANSFER_BUSY =((byte)0x22);
public static final byte INVALID_MESSAGE_CRC =((byte)0x26); // returned if there is a framing error on an incomming message
public static final byte MESSAGE_SIZE_EXCEEDS_LIMIT =((byte)0x27); // returned if a data message is provided that is too large
public static final byte INVALID_MESSAGE =((byte)0x28); // returned when the message has an invalid parameter
public static final byte INVALID_NETWORK_NUMBER =((byte)0x29); // returned when an invalid network number is provided
public static final byte INVALID_LIST_ID =((byte)0x30); // returned when the provided list ID or size exceeds the limit
public static final byte INVALID_SCAN_TX_CHANNEL =((byte)0x31); // returned when attempting to transmit on channel 0 when in scan mode
public static final byte INVALID_PARAMETER_PROVIDED =((byte)0x33); // returned when an invalid parameter is specified in a configuration message
public static final byte EVENT_SERIAL_QUE_OVERFLOW =((byte)0x34);
public static final byte EVENT_QUE_OVERFLOW =((byte)0x35); // ANT event que has overflowed and lost 1 or more events
public static final byte EVENT_CLK_ERROR =((byte)0x36); // debug event for XOSC16M on LE1
public static final byte SCRIPT_FULL_ERROR =((byte)0x40); // error writing to script, memory is full
public static final byte SCRIPT_WRITE_ERROR =((byte)0x41); // error writing to script, bytes not written correctly
public static final byte SCRIPT_INVALID_PAGE_ERROR =((byte)0x42); // error accessing script page
public static final byte SCRIPT_LOCKED_ERROR =((byte)0x43); // the scripts are locked and can't be dumped
public static final byte NO_RESPONSE_MESSAGE =((byte)0x50); // returned to the Command_SerialMessageProcess function, so no reply message is generated
public static final byte RETURN_TO_MFG =((byte)0x51); // default return to any mesg when the module determines that the mfg procedure has not been fully completed
public static final byte FIT_ACTIVE_SEARCH_TIMEOUT =((byte)0x60); // Fit1 only event added for timeout of the pairing state after the Fit module becomes active
public static final byte FIT_WATCH_PAIR =((byte)0x61); // Fit1 only
public static final byte FIT_WATCH_UNPAIR =((byte)0x62); // Fit1 only
public static final byte USB_STRING_WRITE_FAIL =((byte)0x70);
// Internal only events below this point
public static final byte INTERNAL_ONLY_EVENTS =((byte)0x80);
public static final byte EVENT_RX =((byte)0x80); // INTERNAL: Event for a receive message
public static final byte EVENT_NEW_CHANNEL =((byte)0x81); // INTERNAL: EVENT for a new active channel
public static final byte EVENT_PASS_THRU =((byte)0x82); // INTERNAL: Event to allow an upper stack events to pass through lower stacks
public static final byte EVENT_BLOCKED =((byte)0xFF); // INTERNAL: Event to replace any event we do not wish to go out, will also zero the size of the Tx message
///////////////////////////////////////////////////////
// Script Command Codes
///////////////////////////////////////////////////////
public static final byte SCRIPT_CMD_FORMAT =((byte)0x00);
public static final byte SCRIPT_CMD_DUMP =((byte)0x01);
public static final byte SCRIPT_CMD_SET_DEFAULT_SECTOR =((byte)0x02);
public static final byte SCRIPT_CMD_END_SECTOR =((byte)0x03);
public static final byte SCRIPT_CMD_END_DUMP =((byte)0x04);
public static final byte SCRIPT_CMD_LOCK =((byte)0x05);
///////////////////////////////////////////////////////
// Reset Mesg Codes
///////////////////////////////////////////////////////
public static final byte RESET_FLAGS_MASK =((byte)0xE0);
public static final byte RESET_SUSPEND =((byte)0x80); // this must follow bitfield def
public static final byte RESET_SYNC =((byte)0x40); // this must follow bitfield def
public static final byte RESET_CMD =((byte)0x20); // this must follow bitfield def
public static final byte RESET_WDT =((byte)0x02);
public static final byte RESET_RST =((byte)0x01);
public static final byte RESET_POR =((byte)0x00);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,34 @@
/*
* Copyright 2010 Dynastream Innovations 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.dsi.ant;
/**
* Manages the ANT Interface
*
* @hide
*/
public interface AntInterfaceIntent {
public static final String STATUS = "com.dsi.ant.rx.intent.STATUS";
public static final String ANT_MESSAGE = "com.dsi.ant.intent.ANT_MESSAGE";
public static final String ANT_ENABLED_ACTION = "com.dsi.ant.intent.action.ANT_ENABLED";
public static final String ANT_DISABLED_ACTION = "com.dsi.ant.intent.action.ANT_DISABLED";
public static final String ANT_RESET_ACTION = "com.dsi.ant.intent.action.ANT_RESET";
public static final String ANT_RX_MESSAGE_ACTION = "com.dsi.ant.intent.action.ANT_RX_MESSAGE_ACTION";
}
+279
View File
@@ -0,0 +1,279 @@
/*
* Copyright 2010 Dynastream Innovations 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.dsi.ant;
/**
* Public API for controlling the Ant Service.
* AntMesg contains definitions for ANT message IDs
*
* @hide
*/
public class AntMesg {
/////////////////////////////////////////////////////////////////////////////
// HCI VS Message Format
// Messages are in the format:
//
// Outgoing ANT messages (host -> ANT chip)
// 01 D1 FD XX YY YY II JJ ------
// ^ ^ ^ ^
// | HCI framing | | ANT Mesg |
//
// where: 01 is the 1 byte HCI packet Identifier (HCI Command packet)
// D1 FD is the 2 byte HCI op code (0xFDD1 stored in little endian)
// XX is the 1 byte Length of all parameters in bytes (number of bytes in the HCI packet after this byte)
// YY YY is the 2 byte Parameter describing the length of the entire ANT message (II JJ ------) stored in little endian
// II is the 1 byte size of the ANT message (0-249)
// JJ is the 1 byte ID of the ANT message (1-255, 0 is invalid)
// ------ is the data of the ANT message (0-249 bytes of data)
//
// Incoming HCI Command Complete for ANT message command (host <- ANT chip)
// 04 0E 04 01 D1 FD ZZ
//
// where: 04 is the 1 byte HCI packet Identifier (HCI Event packet)
// 0E is the 1 byte HCI event (Command Complete)
// 04 is the 1 byte Length of all parameters in bytes (there are 4 bytes)
// 01 is the 1 byte Number of parameters in the packet (there is 1 parameter)
// D1 FD is the 2 byte HCI Op code of the command (0xFDD1 stored in little endian)
// ZZ is the 1 byte response to the command (0x00 - Command Successful
// 0x1F - Returned if the receive message queue of the ANT chip is full, the command should be retried
// Other - Any other non-zero response code indicates an error)
//
// Incoming ANT messages (host <- ANT chip)
// 04 FF XX 00 05 YY YY II JJ ------
// ^ ^ ^ ^
// | HCI framing | | ANT Mesg |
//
// where: 04 is the 1 byte HCI packet Identifier (HCI Event packet)
// FF is the 1 byte HCI event code (0xFF Vendor Specific event)
// XX is the 1 byte Length of all parameters in bytes (number of bytes in the HCI packet after this byte)
// 00 05 is the 2 byte vendor specific event code for ANT messages (0x0500 stored in little endian)
// YY YY is the 2 byte Parameter describing the length of the entire ANT message (II JJ ------) stored in little endian
// II is the 1 byte size of the ANT message (0-249)
// JJ is the 1 byte ID of the ANT message (1-255, 0 is invalid)
// ------ is the data of the ANT message (0-249 bytes of data)
//
/////////////////////////////////////////////////////////////////////////////
public static final byte MESG_SYNC_SIZE =((byte)0); // Ant messages are embedded in HCI messages we do not include a sync byte
public static final byte MESG_SIZE_SIZE =((byte)1);
public static final byte MESG_ID_SIZE =((byte)1);
public static final byte MESG_CHANNEL_NUM_SIZE =((byte)1);
public static final byte MESG_EXT_MESG_BF_SIZE =((byte)1); // NOTE: this could increase in the future
public static final byte MESG_CHECKSUM_SIZE =((byte)0); // Ant messages are embedded in HCI messages we do not include a checksum
public static final byte MESG_DATA_SIZE =((byte)9);
// The largest serial message is an ANT data message with all of the extended fields
public static final byte MESG_ANT_MAX_PAYLOAD_SIZE =AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE;
public static final byte MESG_MAX_EXT_DATA_SIZE =(AntDefine.ANT_EXT_MESG_DEVICE_ID_FIELD_SIZE + AntDefine.ANT_EXT_STRING_SIZE); // ANT device ID (4 bytes) + Padding for ANT EXT string size(19 bytes)
public static final byte MESG_MAX_DATA_SIZE =(MESG_ANT_MAX_PAYLOAD_SIZE + MESG_EXT_MESG_BF_SIZE + MESG_MAX_EXT_DATA_SIZE); // ANT data payload (8 bytes) + extended bitfield (1 byte) + extended data (10 bytes)
public static final byte MESG_MAX_SIZE_VALUE =(MESG_MAX_DATA_SIZE + MESG_CHANNEL_NUM_SIZE); // this is the maximum value that the serial message size value is allowed to be
public static final byte MESG_BUFFER_SIZE =(MESG_SIZE_SIZE + MESG_ID_SIZE + MESG_CHANNEL_NUM_SIZE + MESG_MAX_DATA_SIZE + MESG_CHECKSUM_SIZE);
public static final byte MESG_FRAMED_SIZE =(MESG_ID_SIZE + MESG_CHANNEL_NUM_SIZE + MESG_MAX_DATA_SIZE);
public static final byte MESG_HEADER_SIZE =(MESG_SYNC_SIZE + MESG_SIZE_SIZE + MESG_ID_SIZE);
public static final byte MESG_FRAME_SIZE =(MESG_HEADER_SIZE + MESG_CHECKSUM_SIZE);
public static final byte MESG_MAX_SIZE =(MESG_MAX_DATA_SIZE + MESG_FRAME_SIZE);
public static final byte MESG_SIZE_OFFSET =(MESG_SYNC_SIZE);
public static final byte MESG_ID_OFFSET =(MESG_SYNC_SIZE + MESG_SIZE_SIZE);
public static final byte MESG_DATA_OFFSET =(MESG_HEADER_SIZE);
public static final byte MESG_RECOMMENDED_BUFFER_SIZE =((byte) 64); // This is the recommended size for serial message buffers if there are no RAM restrictions on the system
//////////////////////////////////////////////
// Message ID's
//////////////////////////////////////////////
public static final byte MESG_INVALID_ID =((byte)0x00);
public static final byte MESG_EVENT_ID =((byte)0x01);
public static final byte MESG_VERSION_ID =((byte)0x3E);
public static final byte MESG_RESPONSE_EVENT_ID =((byte)0x40);
public static final byte MESG_UNASSIGN_CHANNEL_ID =((byte)0x41);
public static final byte MESG_ASSIGN_CHANNEL_ID =((byte)0x42);
public static final byte MESG_CHANNEL_MESG_PERIOD_ID =((byte)0x43);
public static final byte MESG_CHANNEL_SEARCH_TIMEOUT_ID =((byte)0x44);
public static final byte MESG_CHANNEL_RADIO_FREQ_ID =((byte)0x45);
public static final byte MESG_NETWORK_KEY_ID =((byte)0x46);
public static final byte MESG_RADIO_TX_POWER_ID =((byte)0x47);
public static final byte MESG_RADIO_CW_MODE_ID =((byte)0x48);
public static final byte MESG_SYSTEM_RESET_ID =((byte)0x4A);
public static final byte MESG_OPEN_CHANNEL_ID =((byte)0x4B);
public static final byte MESG_CLOSE_CHANNEL_ID =((byte)0x4C);
public static final byte MESG_REQUEST_ID =((byte)0x4D);
public static final byte MESG_BROADCAST_DATA_ID =((byte)0x4E);
public static final byte MESG_ACKNOWLEDGED_DATA_ID =((byte)0x4F);
public static final byte MESG_BURST_DATA_ID =((byte)0x50);
public static final byte MESG_CHANNEL_ID_ID =((byte)0x51);
public static final byte MESG_CHANNEL_STATUS_ID =((byte)0x52);
public static final byte MESG_RADIO_CW_INIT_ID =((byte)0x53);
public static final byte MESG_CAPABILITIES_ID =((byte)0x54);
public static final byte MESG_STACKLIMIT_ID =((byte)0x55);
public static final byte MESG_SCRIPT_DATA_ID =((byte)0x56);
public static final byte MESG_SCRIPT_CMD_ID =((byte)0x57);
public static final byte MESG_ID_LIST_ADD_ID =((byte)0x59);
public static final byte MESG_ID_LIST_CONFIG_ID =((byte)0x5A);
public static final byte MESG_OPEN_RX_SCAN_ID =((byte)0x5B);
public static final byte MESG_EXT_CHANNEL_RADIO_FREQ_ID =((byte)0x5C); // OBSOLETE: (for 905 radio)
public static final byte MESG_EXT_BROADCAST_DATA_ID =((byte)0x5D);
public static final byte MESG_EXT_ACKNOWLEDGED_DATA_ID =((byte)0x5E);
public static final byte MESG_EXT_BURST_DATA_ID =((byte)0x5F);
public static final byte MESG_CHANNEL_RADIO_TX_POWER_ID =((byte)0x60);
public static final byte MESG_GET_SERIAL_NUM_ID =((byte)0x61);
public static final byte MESG_GET_TEMP_CAL_ID =((byte)0x62);
public static final byte MESG_SET_LP_SEARCH_TIMEOUT_ID =((byte)0x63);
public static final byte MESG_SET_TX_SEARCH_ON_NEXT_ID =((byte)0x64);
public static final byte MESG_SERIAL_NUM_SET_CHANNEL_ID_ID =((byte)0x65);
public static final byte MESG_RX_EXT_MESGS_ENABLE_ID =((byte)0x66);
public static final byte MESG_RADIO_CONFIG_ALWAYS_ID =((byte)0x67);
public static final byte MESG_ENABLE_LED_FLASH_ID =((byte)0x68);
public static final byte MESG_XTAL_ENABLE_ID =((byte)0x6D);
public static final byte MESG_STARTUP_MESG_ID =((byte)0x6F);
public static final byte MESG_AUTO_FREQ_CONFIG_ID =((byte)0x70);
public static final byte MESG_PROX_SEARCH_CONFIG_ID =((byte)0x71);
public static final byte MESG_EVENT_BUFFERING_CONFIG_ID =((byte)0x74);
public static final byte MESG_CUBE_CMD_ID =((byte)0x80);
public static final byte MESG_GET_PIN_DIODE_CONTROL_ID =((byte)0x8D);
public static final byte MESG_PIN_DIODE_CONTROL_ID =((byte)0x8E);
public static final byte MESG_FIT1_SET_AGC_ID =((byte)0x8F);
public static final byte MESG_FIT1_SET_EQUIP_STATE_ID =((byte)0x91); // *** CONFLICT: w/ Sensrcore, Fit1 will never have sensrcore enabled
// Sensrcore Messages
public static final byte MESG_SET_CHANNEL_INPUT_MASK_ID =((byte)0x90);
public static final byte MESG_SET_CHANNEL_DATA_TYPE_ID =((byte)0x91);
public static final byte MESG_READ_PINS_FOR_SECT_ID =((byte)0x92);
public static final byte MESG_TIMER_SELECT_ID =((byte)0x93);
public static final byte MESG_ATOD_SETTINGS_ID =((byte)0x94);
public static final byte MESG_SET_SHARED_ADDRESS_ID =((byte)0x95);
public static final byte MESG_ATOD_EXTERNAL_ENABLE_ID =((byte)0x96);
public static final byte MESG_ATOD_PIN_SETUP_ID =((byte)0x97);
public static final byte MESG_SETUP_ALARM_ID =((byte)0x98);
public static final byte MESG_ALARM_VARIABLE_MODIFY_TEST_ID =((byte)0x99);
public static final byte MESG_PARTIAL_RESET_ID =((byte)0x9A);
public static final byte MESG_OVERWRITE_TEMP_CAL_ID =((byte)0x9B);
public static final byte MESG_SERIAL_PASSTHRU_SETTINGS_ID =((byte)0x9C);
public static final byte MESG_BIST_ID =((byte)0xAA);
public static final byte MESG_UNLOCK_INTERFACE_ID =((byte)0xAD);
public static final byte MESG_SERIAL_ERROR_ID =((byte)0xAE);
public static final byte MESG_SET_ID_STRING_ID =((byte)0xAF);
public static final byte MESG_PORT_GET_IO_STATE_ID =((byte)0xB4);
public static final byte MESG_PORT_SET_IO_STATE_ID =((byte)0xB5);
public static final byte MESG_SLEEP_ID =((byte)0xC5);
public static final byte MESG_GET_GRMN_ESN_ID =((byte)0xC6);
public static final byte MESG_SET_USB_INFO_ID =((byte)0xC7);
public static final byte MESG_COMMAND_COMPLETE_RESPONSE_ID =((byte)0xC8);
//////////////////////////////////////////////
// Command complete results
//////////////////////////////////////////////
public static final byte MESG_COMMAND_COMPLETE_SUCCESS =((byte)0x00);
public static final byte MESG_COMMAND_COMPLETE_RETRY =((byte)0x1F);
//////////////////////////////////////////////
// Message Sizes
//////////////////////////////////////////////
public static final byte MESG_INVALID_SIZE =((byte)0);
public static final byte MESG_VERSION_SIZE =((byte)13);
public static final byte MESG_RESPONSE_EVENT_SIZE =((byte)3);
public static final byte MESG_CHANNEL_STATUS_SIZE =((byte)2);
public static final byte MESG_UNASSIGN_CHANNEL_SIZE =((byte)1);
public static final byte MESG_ASSIGN_CHANNEL_SIZE =((byte)3);
public static final byte MESG_CHANNEL_ID_SIZE =((byte)5);
public static final byte MESG_CHANNEL_MESG_PERIOD_SIZE =((byte)3);
public static final byte MESG_CHANNEL_SEARCH_TIMEOUT_SIZE =((byte)2);
public static final byte MESG_CHANNEL_RADIO_FREQ_SIZE =((byte)2);
public static final byte MESG_CHANNEL_RADIO_TX_POWER_SIZE =((byte)2);
public static final byte MESG_NETWORK_KEY_SIZE =((byte)9);
public static final byte MESG_RADIO_TX_POWER_SIZE =((byte)2);
public static final byte MESG_RADIO_CW_MODE_SIZE =((byte)3);
public static final byte MESG_RADIO_CW_INIT_SIZE =((byte)1);
public static final byte MESG_SYSTEM_RESET_SIZE =((byte)1);
public static final byte MESG_OPEN_CHANNEL_SIZE =((byte)1);
public static final byte MESG_CLOSE_CHANNEL_SIZE =((byte)1);
public static final byte MESG_REQUEST_SIZE =((byte)2);
public static final byte MESG_CAPABILITIES_SIZE =((byte)6);
public static final byte MESG_STACKLIMIT_SIZE =((byte)2);
public static final byte MESG_SCRIPT_DATA_SIZE =((byte)10);
public static final byte MESG_SCRIPT_CMD_SIZE =((byte)3);
public static final byte MESG_ID_LIST_ADD_SIZE =((byte)6);
public static final byte MESG_ID_LIST_CONFIG_SIZE =((byte)3);
public static final byte MESG_OPEN_RX_SCAN_SIZE =((byte)1);
public static final byte MESG_EXT_CHANNEL_RADIO_FREQ_SIZE =((byte)3);
public static final byte MESG_RADIO_CONFIG_ALWAYS_SIZE =((byte)2);
public static final byte MESG_RX_EXT_MESGS_ENABLE_SIZE =((byte)2);
public static final byte MESG_SET_TX_SEARCH_ON_NEXT_SIZE =((byte)2);
public static final byte MESG_SET_LP_SEARCH_TIMEOUT_SIZE =((byte)2);
public static final byte MESG_SERIAL_NUM_SET_CHANNEL_ID_SIZE =((byte)3);
public static final byte MESG_ENABLE_LED_FLASH_SIZE =((byte)2);
public static final byte MESG_GET_SERIAL_NUM_SIZE =((byte)4);
public static final byte MESG_GET_TEMP_CAL_SIZE =((byte)4);
public static final byte MESG_XTAL_ENABLE_SIZE =((byte)1);
public static final byte MESG_STARTUP_MESG_SIZE =((byte)1);
public static final byte MESG_AUTO_FREQ_CONFIG_SIZE =((byte)4);
public static final byte MESG_PROX_SEARCH_CONFIG_SIZE =((byte)2);
public static final byte MESG_GET_PIN_DIODE_CONTROL_SIZE =((byte)1);
public static final byte MESG_PIN_DIODE_CONTROL_ID_SIZE =((byte)2);
public static final byte MESG_FIT1_SET_EQUIP_STATE_SIZE =((byte)2);
public static final byte MESG_FIT1_SET_AGC_SIZE =((byte)3);
public static final byte MESG_BIST_SIZE =((byte)6);
public static final byte MESG_UNLOCK_INTERFACE_SIZE =((byte)1);
public static final byte MESG_SET_SHARED_ADDRESS_SIZE =((byte)3);
public static final byte MESG_GET_GRMN_ESN_SIZE =((byte)5);
public static final byte MESG_PORT_SET_IO_STATE_SIZE =((byte)5);
public static final byte MESG_EVENT_BUFFERING_CONFIG_SIZE =((byte)6);
public static final byte MESG_SLEEP_SIZE =((byte)1);
public static final byte MESG_EXT_DATA_SIZE =((byte)13);
protected AntMesg()
{ }
}
+70
View File
@@ -0,0 +1,70 @@
/*
* Copyright 2010 Dynastream Innovations 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.dsi.ant;
/**
* The Android Ant API is not finalized, and *will* be updated and expanded.
* This is the base level ANT messaging API and gives any application full
* control over the ANT radio HW. Caution should be exercised when using
* this interface.
*
* Public API for controlling the Ant Service.
*
* {@hide}
*/
interface IAnt {
// Since version 1 (1.0):
boolean enable();
boolean disable();
boolean isEnabled();
boolean ANTTxMessage(in byte[] message);
boolean ANTResetSystem();
boolean ANTUnassignChannel(byte channelNumber);
boolean ANTAssignChannel(byte channelNumber, byte channelType, byte networkNumber);
boolean ANTSetChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType);
boolean ANTSetChannelPeriod(byte channelNumber, int channelPeriod);
boolean ANTSetChannelRFFreq(byte channelNumber, byte radioFrequency);
boolean ANTSetChannelSearchTimeout(byte channelNumber, byte searchTimeout);
boolean ANTSetLowPriorityChannelSearchTimeout(byte channelNumber, byte searchTimeout);
boolean ANTSetProximitySearch(byte channelNumber, byte searchThreshold);
boolean ANTSetChannelTxPower(byte channelNumber, byte txPower);
boolean ANTAddChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType, byte listIndex);
boolean ANTConfigList(byte channelNumber, byte listSize, byte exclude);
boolean ANTOpenChannel(byte channelNumber);
boolean ANTCloseChannel(byte channelNumber);
boolean ANTRequestMessage(byte channelNumber, byte messageID);
boolean ANTSendBroadcastData(byte channelNumber, in byte[] txBuffer);
boolean ANTSendAcknowledgedData(byte channelNumber, in byte[] txBuffer);
boolean ANTSendBurstTransferPacket(byte control, in byte[] txBuffer);
int ANTSendBurstTransfer(byte channelNumber, in byte[] txBuffer);
int ANTTransmitBurst(byte channelNumber, in byte[] txBuffer, int initialPacket, boolean containsEndOfBurst);
// Since version 4 (1.3):
boolean ANTConfigEventBuffering(int screenOnFlushTimerInterval, int screenOnFlushBufferThreshold, int screenOffFlushTimerInterval, int screenOffFlushBufferThreshold);
// Since version 2 (1.1):
boolean ANTDisableEventBuffering();
// Since version 3 (1.2):
int getServiceLibraryVersionCode();
String getServiceLibraryVersionName();
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010 Dynastream Innovations 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.dsi.ant;
/**
*
* API for configuring the non-ANT components of the ANT Service.
*
* {@hide}
*/
interface IServiceSettings {
void debugLogging(boolean debug);
boolean setNumCombinedBurstPackets(int numPackets);
int getNumCombinedBurstPackets();
}
@@ -0,0 +1,62 @@
/*
* 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;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
/**
* A preference for an ANT device pairing.
* Currently this shows the ID and lets the user clear that ID for future pairing.
* TODO: Support pairing from this preference.
*
* @author Sandor Dornbush
*/
public class AntPreference extends Preference {
public AntPreference(Context context) {
super(context);
init();
}
public AntPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
int sensorId = getPersistedInt(0);
if (sensorId == 0) {
setSummary(R.string.settings_ant_not_paired);
} else {
setSummary(String.format(getContext().getString(R.string.settings_ant_paired), sensorId));
}
// Add actions to allow repairing.
setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AntPreference.this.persistInt(0);
setSummary(R.string.settings_ant_not_paired);
return true;
}
});
}
}
@@ -252,7 +252,7 @@ public class ChartActivity extends Activity implements
setVolumeControlStream(volumeStream);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_elevation);
setContentView(R.layout.mytracks_charts);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
cv = new ChartView(this);
cv.setMode(this.mode);
@@ -21,6 +21,7 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import java.text.DecimalFormat;
@@ -36,16 +37,96 @@ public class ChartValueSeries {
private final Path path = new Path();
private final Paint fillPaint;
private final Paint strokePaint;
private final int rounding;
private final Paint labelPaint;
private final ZoomSettings zoomSettings;
private String title;
private double min = 0;
private double max = 1;
private int effectiveMax = 0;
private int absoluteMax = Integer.MIN_VALUE;
private int effectiveMin = 0;
private double spread = 0;
private double min;
private double max = 1.0;
private int effectiveMax;
private int effectiveMin;
private double spread;
private int interval;
private boolean enabled = true;
/**
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
*/
public static class ZoomSettings {
private int intervals;
private final int absoluteMin;
private final int absoluteMax;
private final int[] zoomLevels;
public ZoomSettings(int intervals, int[] zoomLevels) {
this.intervals = intervals;
this.absoluteMin = Integer.MAX_VALUE;
this.absoluteMax = Integer.MIN_VALUE;
this.zoomLevels = zoomLevels;
checkArgs();
}
public ZoomSettings(int intervals, int absoluteMin, int absoluteMax, int[] zoomLevels) {
this.intervals = intervals;
this.absoluteMin = absoluteMin;
this.absoluteMax = absoluteMax;
this.zoomLevels = zoomLevels;
checkArgs();
}
private void checkArgs() {
if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
}
for (int i = 1; i < zoomLevels.length; ++i) {
if (zoomLevels[i] <= zoomLevels[i - 1]) {
throw new IllegalArgumentException("Expecting zoom levels in ascending order");
}
}
}
public int getIntervals() {
return intervals;
}
public int getAbsoluteMin() {
return absoluteMin;
}
public int getAbsoluteMax() {
return absoluteMax;
}
public int[] getZoomLevels() {
return zoomLevels;
}
/**
* Calculates the interval between markings given the min and max values.
* This function attempts to find the smallest zoom level that fits [min,max] after rounding
* it to the current zoom level.
*
* @param min the minimum value in the series
* @param max the maximum value in the series
* @return the calculated interval for the given range
*/
public int calculateInterval(double min, double max) {
min = Math.min(min, absoluteMin);
max = Math.max(max, absoluteMax);
for (int i = 0; i < zoomLevels.length; ++i) {
int zoomLevel = zoomLevels[i];
int roundedMin = (int)(min / zoomLevel) * zoomLevel;
if (roundedMin > min) {
roundedMin -= zoomLevel;
}
double interval = (max - roundedMin) / intervals;
if (zoomLevel >= interval) {
return zoomLevel;
}
}
return zoomLevels[zoomLevels.length - 1];
}
}
/**
* Constructs a new chart value series.
@@ -54,18 +135,30 @@ public class ChartValueSeries {
* @param formatString The format of the decimal format for this series
* @param fill The paint for filling the chart
* @param stroke The paint for stroking the outside the chart, optional
* @param rounding The factor to round the values by
* @param zoomSettings The settings related to zooming
*
* TODO: Get rid of Context and inject appropriate values instead.
*/
public ChartValueSeries(Context context,
String formatString,
Paint fill,
Paint stroke,
int rounding,
int titleId) {
public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
ZoomSettings zoomSettings, int titleId) {
this.format = new DecimalFormat(formatString);
this.fillPaint = fill;
this.strokePaint = stroke;
this.rounding = rounding;
fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setAntiAlias(true);
if (strokeColor != -1) {
strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
// Make a copy of the stroke paint with the default thickness.
labelPaint = new Paint(strokePaint);
strokePaint.setStrokeWidth(2f);
} else {
strokePaint = null;
labelPaint = fillPaint;
}
this.zoomSettings = zoomSettings;
this.title = context.getString(titleId);
}
@@ -97,20 +190,7 @@ public class ChartValueSeries {
* @return The interval between markers
*/
public int getInterval() {
// Try to find 5 even looking intervals.
int interval = (int) (spread / 5);
int minInterval = rounding / 4;
if (interval < minInterval) {
// We won't be able to find 5 even intervals.
return minInterval;
} else if (interval < rounding) {
// The desired interval is less than the rounding value.
// We will have less than 5 intervals.
return rounding;
} else {
// Round the interval.
return (interval / rounding) * rounding;
}
return interval;
}
/**
@@ -125,14 +205,18 @@ public class ChartValueSeries {
min = monitor.getMin();
max = monitor.getMax();
}
min = Math.min(min, zoomSettings.getAbsoluteMin());
max = Math.max(max, zoomSettings.getAbsoluteMax());
this.interval = zoomSettings.calculateInterval(min, max);
// Round it up.
effectiveMax = ((int) (max / rounding)) * rounding + rounding;
effectiveMax = ((int) (max / interval)) * interval + interval;
// Round it down.
effectiveMin = ((int) (min / rounding)) * rounding;
effectiveMin = ((int) (min / interval)) * interval;
if (min < 0) {
effectiveMin -= rounding;
effectiveMin -= interval;
}
spread = getMax() - effectiveMin;
spread = effectiveMax - effectiveMin;
}
/**
@@ -152,16 +236,10 @@ public class ChartValueSeries {
}
/**
* @return The minimum of the rounded up max value and the effective max.
* @return The rounded up maximum value
*/
public int getMax() {
// Return the effective max if no absolute is set
// or the effective max is less than the absolute.
if ((absoluteMax == Integer.MIN_VALUE) || (effectiveMax < absoluteMax)) {
return effectiveMax;
} else {
return absoluteMax;
}
return effectiveMax;
}
/**
@@ -189,9 +267,11 @@ public class ChartValueSeries {
* @return The paint for this series
*/
Paint getPaint() {
return (strokePaint == null)
? fillPaint
: strokePaint;
return strokePaint == null ? fillPaint : strokePaint;
}
public Paint getLabelPaint() {
return labelPaint;
}
/**
@@ -219,14 +299,6 @@ public class ChartValueSeries {
this.enabled = enabled;
}
public int getAbsoluteMax() {
return absoluteMax;
}
public void setAbsoluteMax(int absoluteMax) {
this.absoluteMax = absoluteMax;
}
public boolean hasData() {
return monitor.hasData();
}
@@ -15,6 +15,7 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.ExtremityMonitor;
import com.google.android.apps.mytracks.util.StringUtils;
@@ -25,9 +26,11 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -60,14 +63,35 @@ public class ChartView extends View {
*/
private int zoomLevel = 1;
private final int minZoomLevel = 1;
private int maxZoomLevel = 5;
private int maxZoomLevel = 10;
private static final int MAX_INTERVALS = 5;
/*
* Borders, margins, dimensions (in pixels):
*/
private int leftBorder = -1;
/**
* Unscaled top border of the chart.
*/
private static final int TOP_BORDER = 15;
private static final int BOTTOM_BORDER = 135;
/**
* Device scaled top border of the chart.
*/
private int topBorder;
/**
* Unscaled bottom border of the chart.
*/
private static final float BOTTOM_BORDER = 40;
/**
* Device scaled bottom border of the chart.
*/
private int bottomBorder;
private static final int RIGHT_BORDER = 17;
/** Space to leave for drawing the unit labels */
private static final int UNIT_BORDER = 15;
@@ -103,6 +127,7 @@ public class ChartView extends View {
private final Paint borderPaint = new Paint();
private final Paint labelPaint = new Paint();
private final Paint gridPaint = new Paint();
private final Paint gridBarPaint = new Paint();
private final Paint clearPaint = new Paint();
private final Drawable pointer;
private final Drawable statsMarker;
@@ -123,8 +148,6 @@ public class ChartView extends View {
private boolean metricUnits = true;
private boolean showPointer = false;
private boolean yLabelMask[] = null;
/** Display chart versus distance or time */
public enum Mode {
BY_DISTANCE, BY_TIME
@@ -149,6 +172,9 @@ public class ChartView extends View {
gridPaint.setColor(context.getResources().getColor(R.color.gray));
gridPaint.setAntiAlias(false);
gridBarPaint.set(gridPaint);
gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
clearPaint.setStyle(Style.FILL);
clearPaint.setColor(context.getResources().getColor(R.color.white));
clearPaint.setAntiAlias(false);
@@ -174,45 +200,47 @@ public class ChartView extends View {
public void setUpChartValueSeries(Context context) {
series = new ChartValueSeries[NUM_SERIES];
// Create the paints
Paint fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(R.color.green));
fillPaint.setAntiAlias(true);
// Create the value series
// Create the value series.
series[ELEVATION_SERIES] =
new ChartValueSeries(context,
"###,###",
fillPaint,
null,
100,
R.color.elevation_fill,
R.color.elevation_border,
new ZoomSettings(MAX_INTERVALS,
new int[] {5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000}),
R.string.elevation);
series[SPEED_SERIES] = getSeries(context, R.color.blue_transparent,
R.color.blue, "###,###.0", R.string.speed);
series[POWER_SERIES] = getSeries(context, R.color.red_transparent,
R.color.red, "###,###", R.string.power);
series[POWER_SERIES].setAbsoluteMax(1500);
series[CADENCE_SERIES] = getSeries(context, R.color.purple_transparent,
R.color.purple, "###,###", R.string.cadence);
series[HEART_RATE_SERIES] = getSeries(context, R.color.yellow_transparent,
R.color.yellow, "###,###", R.string.heart_rate);
}
private ChartValueSeries getSeries(Context context,
int fillColor, int strokeColor, String format, int seriesName) {
Paint fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setAntiAlias(true);
Paint strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
return new ChartValueSeries(context, format, fillPaint, strokePaint, 5,
seriesName);
series[SPEED_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.speed_fill,
R.color.speed_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {5, 10, 20, 50}),
R.string.speed);
series[POWER_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.power_fill,
R.color.power_border,
new ZoomSettings(MAX_INTERVALS, 0, 1000, new int[] {5, 50, 100, 200}),
R.string.power);
series[CADENCE_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.cadence_fill,
R.color.cadence_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {5, 10, 25, 50}),
R.string.cadence);
series[HEART_RATE_SERIES] =
new ChartValueSeries(context,
"###,###",
R.color.heartrate_fill,
R.color.heartrate_border,
new ZoomSettings(MAX_INTERVALS, 0, Integer.MIN_VALUE,
new int[] {25, 50}),
R.string.heart_rate);
}
public void clearWaypoints() {
@@ -277,7 +305,7 @@ public class ChartView extends View {
data.add(theData);
addDataPointInternal(theData);
updateDimensions();
setupPath();
setUpPath();
}
private void addDataPointInternal(double[] theData) {
@@ -308,7 +336,7 @@ public class ChartView extends View {
addDataPointInternal(d);
}
updateDimensions();
setupPath();
setUpPath();
}
/**
@@ -342,7 +370,7 @@ public class ChartView extends View {
public void zoomIn() {
if (canZoomIn()) {
zoomLevel++;
setupPath();
setUpPath();
invalidate();
}
}
@@ -359,7 +387,7 @@ public class ChartView extends View {
scrollX = effectiveWidth * (zoomLevel - 1);
scrollTo(scrollX, 0);
}
setupPath();
setUpPath();
invalidate();
}
}
@@ -525,12 +553,12 @@ public class ChartView extends View {
@Override
protected synchronized void onDraw(Canvas c) {
if (w != c.getWidth() || h != c.getHeight()) {
// Dimensions have changed (for example due to orientation change):
// Dimensions have changed (for example due to orientation change).
w = c.getWidth();
h = c.getHeight();
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
effectiveHeight = Math.max(0, h - TOP_BORDER - BOTTOM_BORDER);
setupPath();
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
setUpPath();
}
c.save();
c.drawColor(Color.WHITE);
@@ -540,16 +568,21 @@ public class ChartView extends View {
c.restore();
return;
}
c.save();
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
// Draw the data series
drawGrid(c);
// Draw the data series.
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
cvs.drawPath(c);
}
}
drawGrid(c);
// Draw the waypoints:
// Draw the waypoints.
for (int i = 1; i < waypoints.size(); i++) {
final Waypoint waypoint = waypoints.get(i);
if (waypoint.getLocation() == null) {
@@ -558,8 +591,8 @@ public class ChartView extends View {
c.save();
final float x = getWaypointX(waypoint);
c.drawLine(x, h - BOTTOM_BORDER, x, TOP_BORDER, gridPaint);
c.translate(x - (markerWidth / 2), markerHeight);
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
c.translate(x - markerWidth / 2, markerHeight);
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
statsMarker.draw(c);
} else {
@@ -568,24 +601,27 @@ public class ChartView extends View {
c.restore();
}
// Draw the axis and labels:
c.restore();
// Draw the axis and labels.
drawXLabels(c);
drawXAxis(c);
drawSeriesTitles(c);
yLabelMask = new boolean[effectiveHeight / FONT_HEIGHT + 1];
c.translate(getScrollX(), 0);
drawYAxis(c);
final int spacer = 5;
int x = leftBorder - spacer;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
drawYLabels(cvs, c);
x -= drawYLabels(cvs, c, x) + spacer;
}
}
c.restore();
if (showPointer && data.size() > 0) {
if (showPointer && !data.isEmpty()) {
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
(getY(series[0], data.get(data.size() - 1)[1])
- pointer.getIntrinsicHeight() / 2 - 12));
getY(series[0], data.get(data.size() - 1)[1])
- pointer.getIntrinsicHeight() / 2 - 12);
pointer.draw(c);
}
}
@@ -600,8 +636,8 @@ public class ChartView extends View {
int j = 0;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled() && cvs.hasData()) {
int y = (int) (w * zoomLevel * ((double) (++j) / sections));
c.drawText(cvs.getTitle(), y, TOP_BORDER, cvs.getPaint());
int x = (int) (w * (double) ++j / sections) + getScrollX();
c.drawText(cvs.getTitle(), x, topBorder, cvs.getLabelPaint());
}
}
}
@@ -610,28 +646,35 @@ public class ChartView extends View {
* Sets up the path that is used to draw the histogram in onDraw(). The path
* needs to be updated any time after the data or histogram dimensions change.
*/
private synchronized void setupPath() {
private synchronized void setUpPath() {
for (ChartValueSeries cvs : series) {
cvs.getPath().reset();
}
if (data.size() < 1) {
if (data.isEmpty()) {
return;
}
// All of the data points to the respective series.
for (int i = 0; i < data.size(); i++) {
// TODO: Come up with a better sampling than Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2);
int sampling = 1;
for (int i = 0; i < data.size(); i += sampling) {
double[] d = data.get(i);
int min = Math.min(series.length, d.length - 1);
for (int j = 0; j < min; j++) {
for (int j = 0; j < min; ++j) {
ChartValueSeries cvs = series[j];
Path path = cvs.getPath();
path.lineTo(getX(d[0]), getY(cvs, d[j + 1]));
int x = getX(d[0]);
int y = getY(cvs, d[j + 1]);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
}
// Close the path.
int yCorner = TOP_BORDER + effectiveHeight;
int yCorner = topBorder + effectiveHeight;
int xCorner = getX(data.get(0)[0]);
int min = series.length;
for (int j = 0; j < min; j++) {
@@ -675,14 +718,20 @@ public class ChartView extends View {
for (ChartValueSeries cvs : series) {
cvs.updateDimension();
}
// TODO: This is totally broken. Make sure that we calculate based on measureText for each
// grid line, as the labels may vary across intervals.
int maxLength = 0;
for (ChartValueSeries cvs : series) {
maxLength = Math.max(maxLength,
cvs.getMaxLabelLength());
if (cvs.isEnabled() && cvs.hasData()) {
maxLength += cvs.getMaxLabelLength();
}
}
leftBorder = 4 + 7 * maxLength;
leftBorder = 4 + 8 * maxLength;
effectiveWidth = w - leftBorder - RIGHT_BORDER;
effectiveHeight = h - TOP_BORDER - BOTTOM_BORDER;
float density = getContext().getResources().getDisplayMetrics().density;
bottomBorder = (int) (density * BOTTOM_BORDER);
topBorder = (int) (density * TOP_BORDER);
effectiveHeight = h - topBorder - bottomBorder;
}
private int getX(double distance) {
@@ -690,28 +739,25 @@ public class ChartView extends View {
}
private int getY(ChartValueSeries cvs, double y) {
return TOP_BORDER + effectiveHeight
return topBorder + effectiveHeight
- (int) ((y - cvs.getMin()) * effectiveHeight / cvs.getSpread());
}
private void drawXLabels(Canvas c) {
int numLabels = 0;
double interval = (int) (maxX / zoomLevel / 4);
boolean shortFormat = false;
if (interval < 1) {
interval = .5;
shortFormat = true;
} else if (interval < 5) {
interval = 2;
} else if (interval < 10) {
interval = 5;
} else {
if (interval < 5) {
interval = 2;
} else if (interval < 10) {
interval = 5;
} else {
interval = (interval / 10) * 10;
}
interval = (interval / 10) * 10;
}
drawXLabel(c, 0, shortFormat);
numLabels++;
int numLabels = 1;
for (int i = 1; i * interval < maxX; i++) {
drawXLabel(c, i * interval, shortFormat);
numLabels++;
@@ -721,102 +767,78 @@ public class ChartView extends View {
}
}
private void drawYLabels(ChartValueSeries cvs, Canvas c) {
int numLabels = 0;
drawYLabel(cvs, c, 0);
final int y0 = getY(cvs, 0);
numLabels++;
private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
int interval = cvs.getInterval();
for (int i = 0; i * interval < cvs.getSpread(); i++) {
final int y = i * interval + cvs.getMin();
final int yi = getY(cvs, y);
if (Math.abs(y0 - yi) > 15) {
drawYLabel(cvs, c, y);
numLabels++;
}
}
if (numLabels < 3) {
drawYLabel(cvs, c, cvs.getMax());
float maxTextWidth = 0;
for (int i = 0; i < MAX_INTERVALS; ++i) {
maxTextWidth = Math.max(maxTextWidth, drawYLabel(cvs, c, x, i * interval + cvs.getMin()));
}
return maxTextWidth;
}
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
if (x < 0) {
return;
}
String s =
(mode == Mode.BY_DISTANCE)
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
: StringUtils.formatTime((long) x);
c.drawText(s,
getX(x),
effectiveHeight + UNIT_BORDER + TOP_BORDER,
effectiveHeight + UNIT_BORDER + topBorder,
labelPaint);
}
private void drawYLabel(ChartValueSeries cvs, Canvas c, int y) {
int desiredY =
(int) ((y - cvs.getMin()) * effectiveHeight / cvs.getSpread());
// Make sure we don't write one label on top of another.
int slot = desiredY / FONT_HEIGHT;
if (slot < 0) {
return;
}
if (yLabelMask[slot]) {
desiredY = ++slot * FONT_HEIGHT;
}
desiredY = TOP_BORDER + effectiveHeight + 5 - desiredY;
if (slot < yLabelMask.length) {
yLabelMask[slot] = true;
}
c.drawText(cvs.getFormat().format(y),
2, desiredY,
cvs.getPaint());
private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
(cvs.getInterval() * MAX_INTERVALS));
desiredY = topBorder + effectiveHeight + FONT_HEIGHT / 2 - desiredY - 1;
Paint p = new Paint(cvs.getLabelPaint());
p.setTextAlign(Align.RIGHT);
String text = cvs.getFormat().format(y);
c.drawText(text, x, desiredY, p);
return p.measureText(text);
}
private void drawXAxis(Canvas canvas) {
float rightEdge = getX(maxX);
canvas.drawLine(leftBorder, effectiveHeight + TOP_BORDER, rightEdge,
effectiveHeight + TOP_BORDER, borderPaint);
final int y = effectiveHeight + topBorder;
canvas.drawLine(leftBorder, y, rightEdge, y, borderPaint);
Context c = getContext();
String s =
(mode == Mode.BY_DISTANCE)
? (metricUnits ? c.getString(R.string.kilometer)
: c.getString(R.string.mile)) : (c.getString(R.string.min));
canvas.drawText(s,
rightEdge,
effectiveHeight + .2f * UNIT_BORDER + TOP_BORDER,
labelPaint);
String s = mode == Mode.BY_DISTANCE
? (metricUnits ? c.getString(R.string.kilometer) : c.getString(R.string.mile))
: c.getString(R.string.min);
canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
}
private void drawYAxis(Canvas canvas) {
canvas.drawLine(leftBorder, UNIT_BORDER + TOP_BORDER,
leftBorder, effectiveHeight + TOP_BORDER,
borderPaint);
canvas.drawRect(0, 0,
leftBorder - 1, effectiveHeight + TOP_BORDER + 1,
clearPaint);
leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
clearPaint);
canvas.drawLine(leftBorder, UNIT_BORDER + topBorder,
leftBorder, effectiveHeight + topBorder,
borderPaint);
for (int i = 1; i < MAX_INTERVALS; ++i) {
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
}
Context c = getContext();
// TODO: This should really show units for all series.
String s = metricUnits
? c.getString(R.string.meter)
: c.getString(R.string.feet);
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f,
UNIT_BORDER * .8f + TOP_BORDER, labelPaint);
String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
}
private synchronized void drawGrid(Canvas c) {
if (data.size() < 1) {
if (data.isEmpty()) {
return;
}
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled()) {
int interval = cvs.getInterval();
for (int i = 1; i * interval < cvs.getSpread(); i++) {
int y = getY(cvs, i * interval + cvs.getMin());
c.drawLine(getX(data.get(0)[0]), y,
getX(data.get(data.size() - 1)[0]), y,
cvs.getPaint());
}
}
float rightEdge = getX(maxX);
for (int i = 1; i < MAX_INTERVALS; ++i) {
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
c.drawLine(leftBorder, y, rightEdge, y, gridBarPaint);
}
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -82,7 +83,8 @@ class CheckUnits {
}
private static void recordCheckPerformed(SharedPreferences preferences) {
preferences.edit().putBoolean(PREFERENCE_UNITS_CHECKED, true).commit();
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_UNITS_CHECKED, true));
}
private CheckUnits() {
@@ -15,43 +15,43 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.ResourceUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
/**
* Displays an EULA ("End User License Agreement") that the user has to accept
* before using the application. Your application should call
* {@link Eula#showEula(android.app.Activity)} in the onCreate() method of the
* first activity. If the user accepts the EULA, it will never be shown again.
* If the user refuses, {@link android.app.Activity#finish()} is invoked on your
* activity.
* This class handles display of EULAs ("End User License Agreements") to the
* user.
*/
class Eula {
private static final String PREFERENCE_EULA_ACCEPTED = "eula.accepted";
private static final String PREFERENCES_EULA = "eula";
private Eula() {}
/**
* Displays the EULA if necessary. This method should be called from the
* onCreate() method of your main Activity.
* onCreate() method of your main Activity. If the user accepts, the EULA
* will never be displayed again. If the user refuses, the activity will
* finish (exit).
*
* @param activity The Activity to finish if the user rejects the EULA
*/
static void showEula(final Activity activity) {
static void showEulaRequireAcceptance(final Activity activity) {
final SharedPreferences preferences =
activity.getSharedPreferences(PREFERENCES_EULA, Activity.MODE_PRIVATE);
if (preferences.getBoolean(PREFERENCE_EULA_ACCEPTED, false)) {
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.eula_title);
builder.setCancelable(true);
final AlertDialog.Builder builder = initDialog(activity);
builder.setPositiveButton(R.string.accept,
new DialogInterface.OnClickListener() {
@Override
@@ -72,12 +72,31 @@ class Eula {
refuse(activity);
}
});
builder.setMessage(ResourceUtils.readFile(activity, R.raw.eula));
builder.show();
}
/**
* Display the EULA to the user in an informational context. They won't be
* given the choice of accepting or declining the EULA -- we're simply
* displaying it for them to read.
*/
static void showEula(Context context) {
AlertDialog.Builder builder = initDialog(context);
builder.setPositiveButton(R.string.ok, null);
builder.show();
}
private static AlertDialog.Builder initDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setCancelable(true);
builder.setTitle(R.string.eula_title);
builder.setMessage(ResourceUtils.readFile(context, R.raw.eula));
return builder;
}
private static void accept(Activity activity, SharedPreferences preferences) {
preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true).commit();
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true));
Intent startIntent = new Intent(activity, WelcomeActivity.class);
activity.startActivityForResult(startIntent, MyTracksConstants.WELCOME);
}
@@ -85,7 +104,4 @@ class Eula {
private static void refuse(Activity activity) {
activity.finish();
}
private Eula() {
}
}
@@ -46,6 +46,7 @@ public class ExportAllTracks {
public static final int GPX_OPTION_INDEX = 0;
public static final int KML_OPTION_INDEX = 1;
public static final int CSV_OPTION_INDEX = 2;
public static final int TCX_OPTION_INDEX = 3;
private final Activity activity;
private WakeLock wakeLock;
@@ -67,6 +68,11 @@ public class ExportAllTracks {
case CSV_OPTION_INDEX:
format = TrackFileFormat.CSV;
break;
case TCX_OPTION_INDEX:
format = TrackFileFormat.TCX;
break;
default:
Log.w(MyTracksConstants.TAG, "Unknown export format: " + which);
}
}
};
@@ -23,7 +23,7 @@ import static com.google.android.apps.mytracks.DialogManager.DIALOG_WRITE_PROGRE
import com.google.android.accounts.Account;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.GpxImporter;
@@ -39,6 +39,7 @@ import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -60,6 +61,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -89,10 +91,6 @@ import org.xml.sax.SAXException;
*/
public class MyTracks extends TabActivity implements OnTouchListener,
OnSharedPreferenceChangeListener, ProgressIndicator {
private static final String WAYPOINT_ICON_URL =
"http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png";
/**
* Singleton instance
*/
@@ -252,6 +250,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
Log.d(MyTracksConstants.TAG, "MyTracks.onCreate");
super.onCreate(savedInstanceState);
instance = this;
ApiFeatures apiFeatures = ApiFeatures.getInstance();
if (!MyTracksUtils.isRelease(this)) {
apiFeatures.getApiPlatformAdapter().enableStrictMode();
}
providerUtils = MyTracksProviderUtils.Factory.get(this);
menuManager = new MenuManager(this);
sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
@@ -259,7 +262,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
setVolumeControlStream(volumeStream);
// We don't need a window title bar:
@@ -310,7 +313,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
// This will show the eula until the user accepts or quits the app.
Eula.showEula(this);
Eula.showEulaRequireAcceptance(this);
// Check if we got invoked via the VIEW intent:
Intent intent = getIntent();
@@ -425,7 +428,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
public boolean onTrackballEvent(MotionEvent event) {
if (isRecording()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
insertStatisticsMarker();
try {
insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker.", e);
} catch (IllegalStateException e) {
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker.", e);
}
return true;
}
}
@@ -506,13 +515,14 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_DOCS: {
case MyTracksConstants.AUTHENTICATE_TO_DOCLIST: {
if (resultCode == RESULT_OK) {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(results,
MyTracksConstants.AUTHENTICATE_TO_TRIX, "writely");
MyTracksConstants.AUTHENTICATE_TO_TRIX,
SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
@@ -523,7 +533,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
setProgressValue(30);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(results, MyTracksConstants.SEND_TO_DOCS, "wise");
authenticate(results, MyTracksConstants.SEND_TO_DOCS,
SendToDocs.GDATA_SERVICE_NAME_TRIX);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
@@ -535,9 +546,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
setProgressValue(50);
setProgressMessage(R.string.progress_message_sending_docs);
final long trackId = results.getLongExtra("trackid", selectedTrackId);
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
sendToTrackId = trackId;
final SendToDocs sender = new SendToDocs(this, authMap.get("wise"),
authMap.get("writely"), trackId);
Runnable onCompletion = new Runnable() {
public void run() {
setProgressValue(100);
@@ -591,7 +603,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
}
if (dialogManager.getSendToGoogleDialog().getSendToDocs()) {
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS,
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST,
RESULT_OK, new Intent());
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
@@ -877,90 +889,25 @@ public class MyTracks extends TabActivity implements OnTouchListener,
/**
* Inserts a waypoint marker.
*
* @return the id of the inserted statistics marker, or
* -1 unable to find location
* -2 track recording service is not running?
* -3 remote exception when contacting track recording service
* -4 inserting marker into provider failed
* @return Id of the inserted statistics marker.
* @throws RemoteException If the call on the service failed.
*/
public long insertWaypointMarker() {
Location location = getCurrentLocation();
if (location == null) {
public long insertWaypoint(WaypointCreationRequest request) throws RemoteException {
if (trackRecordingService == null) {
throw new IllegalStateException("The recording service is not bound.");
}
try {
long waypointId = trackRecordingService.insertWaypoint(request);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_statistics_inserted,
Toast.LENGTH_LONG).show();
}
return waypointId;
} catch (RemoteException e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
return -1;
throw e;
}
if (trackRecordingService != null) {
try {
Waypoint wpt = new Waypoint();
wpt.setName(getString(R.string.waypoint));
wpt.setType(Waypoint.TYPE_WAYPOINT);
wpt.setTrackId(recordingTrackId);
wpt.setIcon(WAYPOINT_ICON_URL);
wpt.setLocation(location);
long waypointId = trackRecordingService.insertWaypointMarker(wpt);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_waypoint_inserted,
Toast.LENGTH_LONG).show();
return waypointId;
} else {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker?");
return -4;
}
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker.", e);
}
return -3;
}
return -2;
}
/**
* Inserts a statistics marker.
*
* @return the id of the inserted statistics marker, or
* -1 unable to find location
* -2 track recording service is not running?
* -3 remote exception when contacting track recording service
* -4 inserting marker into provider failed
*/
public long insertStatisticsMarker() {
Location location = getLastLocation();
if (location == null) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
return -1;
}
if (trackRecordingService != null) {
try {
long waypointId =
trackRecordingService.insertStatisticsMarker(location);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_statistics_inserted,
Toast.LENGTH_LONG).show();
return waypointId;
} else {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker?");
return -4;
}
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker?", e);
}
return -3;
}
return -2;
}
/**
@@ -980,7 +927,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
service);
authMap.put(service, auth);
}
Log.d(MyTracksConstants.TAG, "Loggin in to " + service + "...");
Log.d(MyTracksConstants.TAG, "Logging in to " + service + "...");
if (AuthManagerFactory.useModernAuthManager()) {
runOnUiThread(new Runnable() {
@Override
@@ -1096,7 +1043,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
SendToFusionTables.SERVICE_ID);
} else {
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS, RESULT_OK,
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST, RESULT_OK,
new Intent());
}
}
@@ -1108,10 +1055,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* @param trackId the id of the track
*/
public void setSelectedTrackId(final long trackId) {
sharedPreferences
.edit()
.putLong(getString(R.string.selected_track_key), trackId)
.commit();
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
sharedPreferences
.edit()
.putLong(getString(R.string.selected_track_key), trackId));
}
long getSelectedTrackId() {
@@ -50,7 +50,7 @@ public abstract class MyTracksConstants {
public static final int FEATURE_DETAILS = 5;
public static final int START_RECORDING = 6;
public static final int STOP_RECORDING = 7;
public static final int AUTHENTICATE_TO_DOCS = 8;
public static final int AUTHENTICATE_TO_DOCLIST = 8;
public static final int AUTHENTICATE_TO_TRIX = 9;
public static final int SEND_TO_DOCS = 10;
public static final int DELETE_TRACK = 11;
@@ -213,6 +213,9 @@ public abstract class MyTracksConstants {
}
}
public static final String MAPSHOP_BASE_URL =
"http://maps.google.com/maps/ms";
/**
* This is an abstract utility class.
*/
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
@@ -63,7 +64,7 @@ public class MyTracksDeleteAllTracks extends Handler {
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
SharedPreferences.Editor editor = prefs.edit();
editor.putLong(context.getString(R.string.selected_track_key), -1);
editor.commit();
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
if (done != null) {
Handler h = new Handler();
h.post(done);
@@ -42,6 +42,8 @@ import android.widget.ImageView;
public class MyTracksWaypointDetails extends Activity
implements OnClickListener {
public static final String WAYPOINT_ID_EXTRA = "com.google.android.apps.mytracks.WAYPOINT_ID";
/**
* The id of the way point being edited (taken from bundle, "waypointid")
*/
@@ -78,7 +80,7 @@ public class MyTracksWaypointDetails extends Activity
}
// Required extra when launching this intent:
waypointId = getIntent().getLongExtra("waypointid", -1);
waypointId = getIntent().getLongExtra(WAYPOINT_ID_EXTRA, -1);
if (waypointId < 0) {
Log.d(MyTracksConstants.TAG,
"MyTracksWaypointsDetails intent was launched w/o waypoint id.");
@@ -17,6 +17,7 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -28,6 +29,8 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
@@ -87,7 +90,7 @@ public class MyTracksWaypointsList extends ListActivity
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent result = new Intent();
result.putExtra("trackid", trackId);
result.putExtra("waypointid", id);
result.putExtra(MyTracksWaypointDetails.WAYPOINT_ID_EXTRA, id);
setResult(MyTracksConstants.EDIT_WAYPOINT, result);
finish();
}
@@ -103,7 +106,7 @@ public class MyTracksWaypointsList extends ListActivity
case MyTracksConstants.MENU_EDIT: {
Intent intent = new Intent(this, MyTracksWaypointDetails.class);
intent.putExtra("trackid", trackId);
intent.putExtra("waypointid", waypointId);
intent.putExtra(MyTracksWaypointDetails.WAYPOINT_ID_EXTRA, waypointId);
startActivity(intent);
return true;
}
@@ -165,27 +168,34 @@ public class MyTracksWaypointsList extends ListActivity
@Override
public void onClick(View v) {
WaypointCreationRequest request;
switch (v.getId()) {
case R.id.waypointslist_btn_insert_waypoint: {
long id = MyTracks.getInstance().insertWaypointMarker();
if (id >= 0) {
Intent intent = new Intent(this, MyTracksWaypointDetails.class);
intent.putExtra("waypointid", id);
startActivity(intent);
}
case R.id.waypointslist_btn_insert_waypoint:
request = WaypointCreationRequest.DEFAULT_MARKER;
break;
}
case R.id.waypointslist_btn_insert_statistics: {
long id = MyTracks.getInstance().insertStatisticsMarker();
if (id >= 0) {
Intent intent = new Intent(this, MyTracksWaypointDetails.class);
intent.putExtra("waypointid", id);
startActivity(intent);
}
case R.id.waypointslist_btn_insert_statistics:
request = WaypointCreationRequest.DEFAULT_STATISTICS;
break;
}
default:
return;
}
long id;
try {
id = MyTracks.getInstance().insertWaypoint(request);
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "Cannot insert marker.", e);
return;
} catch (IllegalStateException e) {
Log.e(MyTracksConstants.TAG, "Cannot insert marker.", e);
return;
}
if (id < 0) {
Log.e(MyTracksConstants.TAG, "Failed to insert marker.");
return;
}
Intent intent = new Intent(this, MyTracksWaypointDetails.class);
intent.putExtra(MyTracksWaypointDetails.WAYPOINT_ID_EXTRA, id);
startActivity(intent);
}
private void setListAdapter() {
@@ -20,6 +20,7 @@ import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -71,6 +72,12 @@ public class WelcomeActivity extends Activity {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(view);
builder.setPositiveButton(R.string.ok, null);
builder.setNeutralButton(R.string.license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Eula.showEula(WelcomeActivity.this);
}
});
builder.setIcon(R.drawable.arrow_icon);
AlertDialog dialog = builder.create();
dialog.show();
@@ -137,6 +137,7 @@ public class MyTracksProvider extends ContentProvider {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 17) {
// Wipe the old data.
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TRACKPOINTS_TABLE);
@@ -144,11 +145,15 @@ public class MyTracksProvider extends ContentProvider {
db.execSQL("DROP TABLE IF EXISTS " + WAYPOINTS_TABLE);
onCreate(db);
} else {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", adding sensor column.");
if (oldVersion == 17) {
// Incremental updates go here.
// Each time you increase the DB version, add a corresponding if clause.
// Sensor data.
if (oldVersion <= 17) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", adding sensor column.");
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
};
if (oldVersion >= 17) {
db.execSQL("ALTER TABLE " + TRACKS_TABLE
@@ -24,6 +24,7 @@ import android.location.Location;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
@@ -58,8 +59,8 @@ public class GpxTrackWriter implements TrackFormatWriter {
@Override
public void writeHeader() {
if (pw != null) {
pw.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ");
pw.println("standalone=\"yes\"?>");
pw.format("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"yes\"?>\n",
Charset.defaultCharset().name());
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
pw.println("<gpx");
pw.println(" version=\"1.0\"");
@@ -20,26 +20,16 @@ import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.docs.DocsHelper;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ResourceUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.GDataServiceClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.docs.DocumentsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
import com.google.wireless.gdata.maps.xml.XmlMapsGDataParserFactory;
import com.google.wireless.gdata.parser.GDataParser;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import com.google.wireless.gdata.client.GDataServiceClient;
import android.app.Activity;
import android.content.SharedPreferences;
@@ -47,16 +37,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* A helper class used to transmit tracks statistics to Google Docs/Trix.
@@ -64,29 +45,15 @@ import java.text.NumberFormat;
* @author Sandor Dornbush
*/
public class SendToDocs {
private static final NumberFormat LARGE_UNIT_FORMAT =
new DecimalFormat("#,###,###.00");
private static final NumberFormat SMALL_UNIT_FORMAT =
new DecimalFormat("###,###");
private static final String DOCS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full";
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full?"
+ "category=mine,spreadsheet";
private static final String DOCS_SPREADSHEET_URL =
"http://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
private static final String DOCS_SPREADSHEET_URL_FORMAT =
"http://spreadsheets.google.com/feeds/list/%s/%s/private/full";
protected static final String DOCS_WORKSHEETS_URL_FORMAT =
"http://spreadsheets.google.com/feeds/worksheets/%s/private/full";
private static final String CONTENT_TYPE_PARAM = "Content-Type";
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
"application/x-vnd.oasis.opendocument.spreadsheet";
/** The GData service name for Google Spreadsheets (aka Trix) */
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
/** The GData service name for the Google Docs Document List */
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
private final Activity activity;
private final AuthManager wiseAuth;
private final AuthManager writelyAuth;
private final AuthManager trixAuth;
private final AuthManager docListAuth;
private final long trackId;
private final boolean metricUnits;
private final HandlerThread handlerThread;
@@ -94,18 +61,15 @@ public class SendToDocs {
private boolean createdNewSpreadSheet = false;
private String spreadSheetId = null;
private String workSheetId = null;
private boolean success = true;
private String statusMessage = "";
private Runnable onCompletion = null;
public SendToDocs(Activity activity, AuthManager wiseAuth,
AuthManager writelyAuth, long trackId) {
public SendToDocs(Activity activity, AuthManager trixAuth,
AuthManager docListAuth, long trackId) {
this.activity = activity;
this.wiseAuth = wiseAuth;
this.writelyAuth = writelyAuth;
this.trixAuth = trixAuth;
this.docListAuth = docListAuth;
this.trackId = trackId;
SharedPreferences preferences = activity.getSharedPreferences(
@@ -195,22 +159,24 @@ public class SendToDocs {
* @param track the track
*/
private boolean uploadToDocs(Track track) {
GDataWrapper<GDataServiceClient> wiseWrapper = new GDataWrapper<GDataServiceClient>();
wiseWrapper.setAuthManager(wiseAuth);
wiseWrapper.setRetryOnAuthFailure(true);
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
docListWrapper.setAuthManager(docListAuth);
docListWrapper.setRetryOnAuthFailure(true);
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
trixWrapper.setAuthManager(trixAuth);
trixWrapper.setRetryOnAuthFailure(true);
GDataWrapper<GDataServiceClient> writelyWrapper = new GDataWrapper<GDataServiceClient>();
writelyWrapper.setAuthManager(writelyAuth);
writelyWrapper.setRetryOnAuthFailure(true);
DocsHelper docsHelper = new DocsHelper();
GDataClient androidClient = null;
try {
androidClient = GDataClientFactory.getGDataClient(activity);
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
wiseWrapper.setClient(gdataClient);
trixWrapper.setClient(gdataClient);
Log.d(MyTracksConstants.TAG,
"GData connection prepared: " + this.writelyAuth);
"GData connection prepared: " + this.docListAuth);
String sheetTitle = "My Tracks";
if (track.getCategory() != null && !track.getCategory().equals("")) {
@@ -219,15 +185,19 @@ public class SendToDocs {
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
writelyWrapper.setClient(docsGdataClient);
docListWrapper.setClient(docsGdataClient);
// First try to find the spreadsheet:
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
Log.i(MyTracksConstants.TAG, "Spreadsheet lookup failed.");
String spreadsheetId = null;
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "Spreadsheet lookup failed.", e);
return false;
}
if (spreadSheetId == null) {
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(65);
// Waiting a few seconds and trying again. Maybe the server just had a
// hickup (unfortunately that happens quite a lot...).
@@ -236,22 +206,33 @@ public class SendToDocs {
} catch (InterruptedException e) {
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
}
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
Log.i(MyTracksConstants.TAG, "2nd spreadsheet lookup failed.");
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "2nd spreadsheet lookup failed.", e);
return false;
}
}
// We were unable to find an existing spreadsheet, so create a new one.
MyTracks.getInstance().setProgressValue(70);
if (spreadSheetId == null) {
if (spreadsheetId == null) {
Log.i(MyTracksConstants.TAG, "Creating new spreadsheet: " + sheetTitle);
if (!createSpreadSheet(writelyWrapper, sheetTitle)) {
try {
spreadsheetId = docsHelper.createSpreadsheet(activity, docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "Failed to create new spreadsheet "
+ sheetTitle, e);
return false;
}
MyTracks.getInstance().setProgressValue(80);
createdNewSpreadSheet = true;
if (spreadSheetId == null) {
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(85);
// The previous creation might have succeeded even though GData
// reported an error. Seems to be a know bug,
@@ -264,10 +245,16 @@ public class SendToDocs {
} catch (InterruptedException e) {
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
}
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "Failed create-failed lookup", e);
return false;
}
if (spreadSheetId == null) {
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(87);
// Re-try
try {
@@ -275,11 +262,16 @@ public class SendToDocs {
} catch (InterruptedException e) {
Log.e(MyTracksConstants.TAG, "Sleep interrupted", e);
}
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "Failed create-failed relookup", e);
return false;
}
}
if (spreadSheetId == null) {
if (spreadsheetId == null) {
Log.i(MyTracksConstants.TAG,
"Creating new spreadsheet really failed.");
return false;
@@ -287,14 +279,21 @@ public class SendToDocs {
}
}
if (!getWorkSheetId(wiseWrapper)) {
Log.i(MyTracksConstants.TAG, "Looking up worksheet id failed.");
String worksheetId = null;
try {
worksheetId = docsHelper.getWorksheetId(trixWrapper, spreadsheetId);
if (worksheetId == null) {
throw new IOException("Worksheet ID lookup returned empty");
}
} catch (IOException e) {
Log.i(MyTracksConstants.TAG, "Looking up worksheet id failed.", e);
return false;
}
MyTracks.getInstance().setProgressValue(90);
insertRowNet(track, spreadSheetId, workSheetId);
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
track, metricUnits);
Log.i(MyTracksConstants.TAG, "Done uploading to docs.");
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Unable to upload docs.", e);
@@ -306,235 +305,4 @@ public class SendToDocs {
}
return true;
}
private boolean getSpreadsheetId(GDataWrapper<GDataServiceClient> wrapper, final String title) {
spreadSheetId = null;
return wrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client)
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException {
GDataParser listParser;
try {
listParser = client.getParserForFeed(Entry.class,
DOCS_MY_SPREADSHEETS_FEED_URL, writelyAuth.getAuthToken());
listParser.init();
while (listParser.hasMoreData()) {
Entry entry = listParser.readNextEntry(null);
String entryTitle = entry.getTitle();
Log.i(MyTracksConstants.TAG, "Found docs entry: " + entryTitle);
if (entryTitle.equals(title)) {
String entryId = entry.getId();
int lastSlash = entryId.lastIndexOf('/');
spreadSheetId = entryId.substring(lastSlash + 15);
break;
}
}
} catch (ParseException e) {
throw new GDataWrapper.ParseException(e);
} catch (HttpException e) {
throw new GDataWrapper.HttpException(e.getStatusCode(), e.getMessage());
}
}
});
}
private boolean getWorkSheetId(GDataWrapper<GDataServiceClient> wrapper) {
workSheetId = null;
return wrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client)
throws GDataWrapper.AuthenticationException, IOException, GDataWrapper.ParseException {
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadSheetId);
GDataParser sheetParser;
try {
sheetParser = ((SpreadsheetsClient) client).getParserForWorksheetsFeed(uri,
wiseAuth.getAuthToken());
sheetParser.init();
if (!sheetParser.hasMoreData()) {
Log.i(MyTracksConstants.TAG, "Found no worksheets");
return; // failure
}
// just grab the first.
WorksheetEntry worksheetEntry =
(WorksheetEntry) sheetParser.readNextEntry(new WorksheetEntry());
int lastSlash = worksheetEntry.getId().lastIndexOf('/');
workSheetId = worksheetEntry.getId().substring(lastSlash + 1);
} catch (AuthenticationException e) {
throw new GDataWrapper.AuthenticationException(e);
} catch (ParseException e) {
throw new GDataWrapper.ParseException(e);
}
}
});
}
/**
* Creates a new MyTracks spreadsheet with the given name.
*/
private boolean createSpreadSheet(GDataWrapper<GDataServiceClient> writelyWrapper,
final String name) {
spreadSheetId = null;
return writelyWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client) throws IOException {
// Construct data
// Send data
URL url = new URL(DOCS_FEED_URL);
URLConnection conn = url.openConnection();
conn.addRequestProperty(CONTENT_TYPE_PARAM,
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
conn.addRequestProperty("Slug", name);
conn.addRequestProperty("Authorization",
"GoogleLogin auth=" + writelyAuth.getAuthToken());
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
ResourceUtils.readBinaryFileToOutputStream(
activity, R.raw.mytracks_empty_spreadsheet, os);
// Get the response
// TODO: The following is a horrible ugly hack.
// Hopefully we can retire it when there is a proper gdata api.
BufferedReader rd = null;
String line;
StringBuilder resultBuilder = new StringBuilder();
try {
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line = rd.readLine()) != null) {
resultBuilder.append(line);
}
os.close();
rd.close();
} catch (FileNotFoundException e) {
// The GData API sometimes throws an error, even though creation of
// the document succeeded. In that case let's just return. The caller
// then needs to check if the doc actually exists.
return;
} finally {
os.close();
if (rd != null) {
rd.close();
}
}
String result = resultBuilder.toString();
// Try to find the id.
int idTagIndex = result.indexOf("<id>");
if (idTagIndex == -1) {
return;
}
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
if (idTagCloseIndex == -1) {
return;
}
int idStringStart = result.indexOf(DOCS_SPREADSHEET_URL, idTagIndex);
if (idStringStart == -1) {
return;
}
spreadSheetId = result.substring(
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
Log.i(MyTracksConstants.TAG, "Created new spreadsheet: " + spreadSheetId);
}});
}
private void insertRowNet(Track track, String spreadsheetId,
String worksheetId) throws IOException {
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
spreadsheetId, worksheetId);
TripStatistics stats = track.getStatistics();
/* Prepare the Post-Text we are going to send. */
StringBuilder sb = new StringBuilder();
sb.append("<entry xmlns='http://www.w3.org/2005/Atom' ");
sb.append("xmlns:gsx='http://schemas.google.com/spreadsheets/"
+ "2006/extended'>");
appendTag("name", track.getName(), sb);
appendTag("description", track.getDescription(), sb);
appendTag("date", String.format("%tc", stats.getStartTime()), sb);
appendTag("totaltime", StringUtils.formatTimeAlwaysShowingHours(
stats.getTotalTime()), sb);
appendTag("movingtime", StringUtils.formatTimeAlwaysShowingHours(
stats.getMovingTime()), sb);
appendLargeUnitsTag("distance", stats.getTotalDistance() / 1000, sb);
appendTag("distanceunit",
metricUnits
? activity.getString(R.string.kilometer)
: activity.getString(R.string.mile),
sb);
appendLargeUnitsTag("averagespeed", stats.getAverageSpeed() * 3.6, sb);
appendLargeUnitsTag("averagemovingspeed",
stats.getAverageMovingSpeed() * 3.6, sb);
appendLargeUnitsTag("maxspeed", stats.getMaxSpeed() * 3.6, sb);
appendTag("speedunit",
metricUnits
? activity.getString(R.string.kilometer_per_hour)
: activity.getString(R.string.mile_per_hour),
sb);
appendSmallUnitsTag("elevationgain", stats.getTotalElevationGain(), sb);
appendSmallUnitsTag("minelevation", stats.getMinElevation(), sb);
appendSmallUnitsTag("maxelevation", stats.getMaxElevation(), sb);
appendTag("elevationunit",
metricUnits
? activity.getString(R.string.meter)
: activity.getString(R.string.feet),
sb);
if (track.getMapId().length() > 0) {
appendTag("map", SendToFusionTables.getMapVisualizationUrl(track), sb);
}
sb.append("</entry>");
Log.i(MyTracksConstants.TAG,
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
String postText = sb.toString();
Log.i(MyTracksConstants.TAG, postText);
// Send data
// No need for a wrapper because we know that the authorization was good
// enough to get this far
URL url = new URL(worksheetUri);
URLConnection conn = url.openConnection();
conn.addRequestProperty(CONTENT_TYPE_PARAM, ATOM_FEED_MIME_TYPE);
conn.addRequestProperty("Authorization",
"GoogleLogin auth=" + wiseAuth.getAuthToken());
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(postText);
wr.flush();
// Get the response
BufferedReader rd =
new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
// Process line...
Log.i(MyTracksConstants.TAG, "r: " + line);
}
wr.close();
rd.close();
Log.i(MyTracksConstants.TAG, "Post finished.");
}
private void appendTag(String name, String value, StringBuilder sb) {
sb.append("<gsx:");
sb.append(name);
sb.append('>');
sb.append(StringUtils.stringAsCData(value));
sb.append("</gsx:");
sb.append(name);
sb.append('>');
}
private void appendLargeUnitsTag(String name, double d, StringBuilder sb) {
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
appendTag(name, LARGE_UNIT_FORMAT.format(value), sb);
}
private void appendSmallUnitsTag(String name, double d, StringBuilder sb) {
double value = metricUnits ? d : (d * UnitConversions.M_TO_FT);
appendTag(name, SMALL_UNIT_FORMAT.format(value), sb);
}
}
@@ -27,6 +27,7 @@ import android.os.Build;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
@@ -77,7 +78,8 @@ public class TcxTrackWriter implements TrackFormatWriter {
if (pw == null) {
return;
}
pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>");
pw.format("<?xml version=\"1.0\" encoding=\"%s\" standalone=\"no\" ?>\n",
Charset.defaultCharset().name());
pw.print("<TrainingCenterDatabase ");
pw.print("xmlns=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\" ");
pw.print("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
@@ -16,9 +16,11 @@
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
@@ -266,62 +268,80 @@ public class TrackWriter {
private void writeLocations() {
boolean wroteFirst = false;
boolean segmentOpen = false;
Location lastLoc = null, loc = null;
boolean isLastValid = false;
Cursor locationsCursor =
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
return;
}
do {
if (loc == null) loc = new Location("");
providerUtils.fillLocation(locationsCursor, loc);
boolean isValid = MyTracksUtils.isValidLocation(loc);
boolean validSegment = isValid && isLastValid;
if (!wroteFirst && validSegment) {
// Found the first two consecutive points which are valid
writer.writeBeginTrack(lastLoc);
wroteFirst = true;
}
if (validSegment) {
if (!segmentOpen) {
// Start a segment for this point
writer.writeOpenSegment();
segmentOpen = true;
// Write the previous point, which we had previously skipped
writer.writeLocation(lastLoc);
class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
Location currentLocation;
Location lastLocation;
@Override
public Location createLocation() {
if (currentLocation == null) {
currentLocation = new MyTracksLocation("");
}
// Write the current point
writer.writeLocation(loc);
} else {
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
return currentLocation;
}
public void swapLocations() {
Location tmpLoc = lastLocation;
lastLocation = currentLocation;
currentLocation = tmpLoc;
if (currentLocation != null) {
currentLocation.reset();
}
}
// Swap loc and lastLoc (so lastLoc is reused)
Location tmp = lastLoc;
lastLoc = loc;
loc = tmp;
if (loc != null) loc.reset();
isLastValid = isValid;
} while (locationsCursor.moveToNext());
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
}
if (wroteFirst) {
writer.writeEndTrack(lastLoc);
};
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator it = providerUtils.getLocationIterator(track.getId(), 0, false,
locationFactory);
try {
if (!it.hasNext()) {
Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
return;
}
while (it.hasNext()) {
Location loc = it.next();
boolean isValid = MyTracksUtils.isValidLocation(loc);
boolean validSegment = isValid && isLastValid;
if (!wroteFirst && validSegment) {
// Found the first two consecutive points which are valid
writer.writeBeginTrack(locationFactory.lastLocation);
wroteFirst = true;
}
if (validSegment) {
if (!segmentOpen) {
// Start a segment for this point
writer.writeOpenSegment();
segmentOpen = true;
// Write the previous point, which we had previously skipped
writer.writeLocation(locationFactory.lastLocation);
}
// Write the current point
writer.writeLocation(loc);
} else {
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
}
}
locationFactory.swapLocations();
isLastValid = isValid;
}
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
}
if (wroteFirst) {
writer.writeEndTrack(locationFactory.lastLocation);
}
} finally {
it.close();
}
}
}
@@ -0,0 +1,370 @@
/*
* 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.io.docs;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ResourceUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataServiceClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.docs.SpreadsheetsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient.WorksheetEntry;
import com.google.wireless.gdata.parser.GDataParser;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.content.Context;
import android.util.Log;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.atomic.AtomicReference;
/**
* This class contains helper methods for interacting with Google Docs and
* Google Spreadsheets.
*
* @author Sandor Dornbush
* @author Matthew Simmons
*/
public class DocsHelper {
private static final String DOCS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full";
private static final String DOCS_SPREADSHEET_URL =
"http://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
private static final String DOCS_WORKSHEETS_URL_FORMAT =
"http://spreadsheets.google.com/feeds/worksheets/%s/private/full";
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full?category=mine,spreadsheet";
private static final String DOCS_SPREADSHEET_URL_FORMAT =
"http://spreadsheets.google.com/feeds/list/%s/%s/private/full";
private static final String CONTENT_TYPE_PARAM = "Content-Type";
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
"application/x-vnd.oasis.opendocument.spreadsheet";
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
/**
* Creates a new MyTracks spreadsheet with the given name.
*
* @param context The context associated with this request.
* @param docListWrapper The GData handle for the Document List service.
* @param name The name for the newly-created spreadsheet.
* @return The spreadsheet ID, if one is created. {@code null} will be
* returned if a GData error didn't occur, but no spreadsheet ID was
* returned.
*/
public String createSpreadsheet(final Context context,
final GDataWrapper<GDataServiceClient> docListWrapper, final String name) throws IOException {
final AtomicReference<String> idSaver = new AtomicReference<String>();
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client) throws IOException {
// Construct and send request
URL url = new URL(DOCS_FEED_URL);
URLConnection conn = url.openConnection();
conn.addRequestProperty(CONTENT_TYPE_PARAM,
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
conn.addRequestProperty("Slug", name);
conn.addRequestProperty("Authorization",
"GoogleLogin auth=" +
docListWrapper.getAuthManager().getAuthToken());
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
ResourceUtils.readBinaryFileToOutputStream(
context, R.raw.mytracks_empty_spreadsheet, os);
// Get the response
// TODO: The following is a horrible ugly hack.
// Hopefully we can retire it when there is a proper gdata api.
BufferedReader rd = null;
String line;
StringBuilder resultBuilder = new StringBuilder();
try {
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line = rd.readLine()) != null) {
resultBuilder.append(line);
}
os.close();
rd.close();
} catch (FileNotFoundException e) {
// The GData API sometimes throws an error, even though creation of
// the document succeeded. In that case let's just return. The caller
// then needs to check if the doc actually exists.
return;
} finally {
os.close();
if (rd != null) {
rd.close();
}
}
String result = resultBuilder.toString();
// Try to find the id.
int idTagIndex = result.indexOf("<id>");
if (idTagIndex == -1) {
return;
}
int idTagCloseIndex = result.indexOf("</id>", idTagIndex);
if (idTagCloseIndex == -1) {
return;
}
int idStringStart = result.indexOf(DOCS_SPREADSHEET_URL, idTagIndex);
if (idStringStart == -1) {
return;
}
String id = result.substring(
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
Log.i(MyTracksConstants.TAG, "Created new spreadsheet: " + id);
idSaver.set(id);
}});
if (!result) {
throw newIOException(docListWrapper,
"Failed to create new spreadsheet.");
}
return idSaver.get();
}
/**
* Retrieve the ID of a spreadsheet with the given name.
*
* @param docListWrapper The GData handle for the Document List service.
* @param title The name of the spreadsheet whose ID is to be retrieved.
* @return The spreadsheet ID, if it can be retrieved. {@code null} will
* be returned if no spreadsheet exists by the given name.
* @throws IOException If an error occurs during the GData request.
*/
public String requestSpreadsheetId(final GDataWrapper<GDataServiceClient> docListWrapper,
final String title) throws IOException {
final AtomicReference<String> idSaver = new AtomicReference<String>();
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client)
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException {
GDataParser listParser;
try {
listParser = client.getParserForFeed(Entry.class,
DOCS_MY_SPREADSHEETS_FEED_URL,
docListWrapper.getAuthManager().getAuthToken());
listParser.init();
while (listParser.hasMoreData()) {
Entry entry = listParser.readNextEntry(null);
String entryTitle = entry.getTitle();
Log.i(MyTracksConstants.TAG, "Found docs entry: " + entryTitle);
if (entryTitle.equals(title)) {
String entryId = entry.getId();
int lastSlash = entryId.lastIndexOf('/');
idSaver.set(entryId.substring(lastSlash + 15));
break;
}
}
} catch (ParseException e) {
throw new GDataWrapper.ParseException(e);
} catch (HttpException e) {
throw new GDataWrapper.HttpException(e.getStatusCode(), e.getMessage());
}
}
});
if (!result) {
throw newIOException(docListWrapper,
"Failed to retrieve spreadsheet list.");
}
return idSaver.get();
}
/**
* Retrieve the ID of the first worksheet in the named spreadsheet.
*
* @param trixWrapper The GData handle for the spreadsheet service.
* @param spreadsheetId The GData ID for the given spreadsheet.
* @return The worksheet ID, if it can be retrieved. {@code null} will be
* returned if the GData request returns without error, but without an
* ID.
* @throws IOException If an error occurs during the GData request.
*/
public String getWorksheetId(final GDataWrapper<GDataServiceClient> trixWrapper,
final String spreadsheetId) throws IOException {
final AtomicReference<String> idSaver = new AtomicReference<String>();
boolean result = trixWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client)
throws GDataWrapper.AuthenticationException, IOException, GDataWrapper.ParseException, GDataWrapper.HttpException {
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadsheetId);
GDataParser sheetParser;
try {
sheetParser = ((SpreadsheetsClient) client).getParserForWorksheetsFeed(uri,
trixWrapper.getAuthManager().getAuthToken());
sheetParser.init();
if (!sheetParser.hasMoreData()) {
Log.i(MyTracksConstants.TAG, "Found no worksheets");
return;
}
// Grab the first worksheet.
WorksheetEntry worksheetEntry =
(WorksheetEntry) sheetParser.readNextEntry(new WorksheetEntry());
int lastSlash = worksheetEntry.getId().lastIndexOf('/');
idSaver.set(worksheetEntry.getId().substring(lastSlash + 1));
} catch (ParseException e) {
throw new GDataWrapper.ParseException(e);
} catch (AuthenticationException e) {
throw new GDataWrapper.AuthenticationException(e);
}
}
});
if (!result) {
throw newIOException(trixWrapper, "Failed to retrieve worksheet ID.");
}
return idSaver.get();
}
/**
* Add a row to a worksheet containing the stats for a given track.
*
* @param context The context associated with this request.
* @param trixAuth The GData authorization for the spreadsheet service.
* @param spreadsheetId The spreadsheet to be modified.
* @param worksheetId The worksheet to be modified.
* @param track The track whose stats are to be written.
* @param metricUnits True if metric units are to be used. If false,
* imperial units will be used.
* @throws IOException If an error occurs while updating the worksheet.
*/
public void addTrackRow(Context context, AuthManager trixAuth,
String spreadsheetId, String worksheetId, Track track,
boolean metricUnits) throws IOException {
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
spreadsheetId, worksheetId);
TripStatistics stats = track.getStatistics();
String distanceUnit = context.getString(metricUnits ?
R.string.kilometer : R.string.mile);
String speedUnit = context.getString(metricUnits ?
R.string.kilometer_per_hour : R.string.mile_per_hour);
String elevationUnit = context.getString(metricUnits ?
R.string.meter : R.string.feet);
// Prepare the Post-Text we are going to send.
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
.append("name", track.getName())
.append("description", track.getDescription())
.append("date", String.format("%tc", stats.getStartTime()))
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
stats.getTotalTime()))
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
stats.getMovingTime()))
.appendLargeUnits("distance", stats.getTotalDistance() / 1000)
.append("distanceunit", distanceUnit)
.appendLargeUnits("averagespeed", stats.getAverageSpeed() * 3.6)
.appendLargeUnits("averagemovingspeed",
stats.getAverageMovingSpeed() * 3.6)
.appendLargeUnits("maxspeed", stats.getMaxSpeed() * 3.6)
.append("speedunit", speedUnit)
.appendSmallUnits("elevationgain", stats.getTotalElevationGain())
.appendSmallUnits("minelevation", stats.getMinElevation())
.appendSmallUnits("maxelevation", stats.getMaxElevation())
.append("elevationunit", elevationUnit);
if (track.getMapId().length() > 0) {
tagBuilder.append("map", String.format("%s?msa=0&msid=%s",
MyTracksConstants.MAPSHOP_BASE_URL, track.getMapId()));
}
String postText = new StringBuilder()
.append("<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/"
+ "2006/extended'>")
.append(tagBuilder.build())
.append("</entry>")
.toString();
Log.i(MyTracksConstants.TAG,
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
Log.i(MyTracksConstants.TAG, postText);
writeRowData(trixAuth, worksheetUri, postText);
Log.i(MyTracksConstants.TAG, "Post finished.");
}
/**
* Writes spreadsheet row data to the indicated worksheet.
*
* @param trixAuth The GData authorization for the spreadsheet service.
* @param worksheetUri The URI of the worksheet to be altered.
* @param postText The XML tags describing the change to be made.
* @throws IOException Thrown if an error occurs during the write.
*/
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
String postText) throws IOException {
// No need for a wrapper because we know that the authorization was good
// enough to get this far.
URL url = new URL(worksheetUri);
URLConnection conn = url.openConnection();
conn.addRequestProperty(CONTENT_TYPE_PARAM, ATOM_FEED_MIME_TYPE);
conn.addRequestProperty("Authorization",
"GoogleLogin auth=" + trixAuth.getAuthToken());
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(postText);
wr.flush();
// Get the response.
// TODO: Should we parse the response, rather than simply throwing it away?
BufferedReader rd =
new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
// Process line.
Log.i(MyTracksConstants.TAG, "r: " + line);
}
wr.close();
rd.close();
}
private static IOException newIOException(GDataWrapper wrapper,
String message) {
return new IOException(String.format("%s: %d: %s", message,
wrapper.getErrorType(), wrapper.getErrorMessage()));
}
}
@@ -0,0 +1,113 @@
/*
* 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.io.docs;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* <p>This class builds a string of XML tags used to talk to Docs using GData.
*
* <p>Sample Usage:
*
* <code>
* String tags = new DocsTagBuilder(false)
* .append("tagName", "tagValue")
* .appendLargeUnits("bigTagName", 1.0)
* .build();
* </code>
*
* <p>results in:
*
* <code>
* <gsx:tagName><![CDATA[tagValue]]></gsx:tagName>
* <gsx:bigTagName><![CDATA[1.00]]></gsx:bigTagName>
* </code>
*
* @author Matthew Simmons
*/
class DocsTagBuilder {
// TODO(simmonmt): These formats aren't I18N-compatible. Not everyone uses
// commas for thousands and dots for the decimal point.
private static final NumberFormat LARGE_UNIT_FORMAT =
new DecimalFormat("#,###,###.00");
private static final NumberFormat SMALL_UNIT_FORMAT =
new DecimalFormat("###,###");
protected final boolean metricUnits;
protected final StringBuilder stringBuilder;
/**
* @param metricUnits True if metric units are to be used. If false,
* imperial units will be used.
*/
DocsTagBuilder(boolean metricUnits) {
this.metricUnits = metricUnits;
stringBuilder = new StringBuilder();
}
/** Appends a tag containing a string value */
DocsTagBuilder append(String name, String value) {
appendTag(name, value);
return this;
}
/**
* Appends a tag containing a numeric value. The value will be formatted
* according to the large distance of the specified measurement system (i.e.
* kilometers or miles).
*
* @param name The tag name.
* @param d The value to be formatted, in kilometers.
*/
DocsTagBuilder appendLargeUnits(String name, double d) {
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
appendTag(name, LARGE_UNIT_FORMAT.format(value));
return this;
}
/**
* Appends a tag containing a numeric value. The value will be formatted
* according to the small distance of the specified measurement system (i.e.
* meters or feet).
*
* @param name The tag name.
* @param d The value to be formatted, in meters.
*/
DocsTagBuilder appendSmallUnits(String name, double d) {
double value = metricUnits ? d : (d * UnitConversions.M_TO_FT);
appendTag(name, SMALL_UNIT_FORMAT.format(value));
return this;
}
/** Returns a string containing all tags which have been added. */
String build() {
return stringBuilder.toString();
}
private void appendTag(String name, String value) {
stringBuilder.append("<gsx:")
.append(name)
.append(">")
.append(StringUtils.stringAsCData(value))
.append("</gsx:")
.append(name)
.append(">");
}
}
@@ -289,6 +289,10 @@ public class GDataWrapper<C> {
this.auth = auth;
}
public AuthManager getAuthManager() {
return auth;
}
public void setRetryOnAuthFailure(boolean retry) {
retryOnAuthFailure = retry;
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.util.UnitConversions;
import android.util.Log;
@@ -111,7 +112,7 @@ public class SplitManager {
}
if (distance > this.nextSplitDistance) {
service.insertStatisticsMarker(service.getLastLocation());
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
calculateNextSplit();
}
}
@@ -16,6 +16,9 @@
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
/**
* A simple task to insert statistics markers every n minutes.
* @author Sandor Dornbush
@@ -24,7 +27,7 @@ public class TimeSplitTask implements PeriodicTask {
@Override
public void run(TrackRecordingService service) {
service.insertStatisticsMarker(service.getLastLocation());
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
}
@Override
@@ -26,6 +26,7 @@ import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
@@ -70,9 +71,6 @@ import java.util.TimerTask;
*/
public class TrackRecordingService extends Service implements LocationListener {
private static final String STATISTICS_ICON_URL =
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
private NotificationManager notificationManager;
@@ -429,7 +427,7 @@ public class TrackRecordingService extends Service implements LocationListener {
lastValidLocation = null;
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
if (waypoint != null) {
if (waypoint != null && waypoint.getStatistics() != null) {
currentWaypointId = waypoint.getId();
waypointStatsBuilder = new TripStatisticsBuilder(
waypoint.getStatistics());
@@ -646,7 +644,6 @@ public class TrackRecordingService extends Service implements LocationListener {
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
splitManager = new SplitManager(this);
sensorManager = SensorManagerFactory.getSensorManager(this);
prefManager = new PreferenceManager(this);
registerLocationListener();
@@ -733,7 +730,7 @@ public class TrackRecordingService extends Service implements LocationListener {
splitManager.shutdown();
splitManager = null;
if (sensorManager != null) {
sensorManager.onDestroy();
sensorManager.shutdown();
sensorManager = null;
}
@@ -860,50 +857,62 @@ public class TrackRecordingService extends Service implements LocationListener {
return isRecording;
}
public long insertWaypointMarker(Waypoint waypoint) {
public long insertWaypoint(WaypointCreationRequest request) {
if (!isRecording()) {
throw new IllegalStateException(
"Unable to insert waypoint marker while not recording!");
}
if (waypoint.getLocation() == null) {
if (lastValidLocation == null) {
Log.w(MyTracksConstants.TAG, "Cannot insert waypoint with no location");
return -1;
}
waypoint.setLocation(lastValidLocation);
if (request == null) {
request = WaypointCreationRequest.DEFAULT_MARKER;
}
if (waypoint.getTrackId() < 0) {
waypoint.setTrackId(recordingTrackId);
Waypoint wpt = new Waypoint();
switch (request.getType()) {
case MARKER:
buildMarker(wpt, request);
break;
case STATISTICS:
buildStatisticsMarker(wpt);
break;
}
waypoint.setLength(length);
waypoint.setDuration(waypoint.getLocation().getTime()
- statsBuilder.getStatistics().getStartTime());
Uri uri = providerUtils.insertWaypoint(waypoint);
wpt.setTrackId(recordingTrackId);
wpt.setLength(length);
if (lastValidLocation != null) {
// A null location is ok, and expected on track start.
wpt.setLocation(lastLocation);
wpt.setDuration(lastLocation.getTime()
- statsBuilder.getStatistics().getStartTime());
}
Uri uri = providerUtils.insertWaypoint(wpt);
return Long.parseLong(uri.getLastPathSegment());
}
private void buildMarker(Waypoint wpt, WaypointCreationRequest request) {
wpt.setType(Waypoint.TYPE_WAYPOINT);
if (request.getIconUrl() == null) {
wpt.setIcon(getString(R.string.waypoint_icon_url));
} else {
wpt.setIcon(request.getIconUrl());
}
if (request.getName() == null) {
wpt.setName(getString(R.string.waypoint));
} else {
wpt.setName(request.getName());
}
if (request.getDescription() != null) {
wpt.setDescription(request.getDescription());
}
}
/**
* Inserts a statistics marker. A statistics marker holds the stats for the
* last segment up to this marker.
* Build a statistics marker.
* A statistics marker holds the stats for the* last segment up to this marker.
*
* @param location the location where to insert
* @param Waypoint The waypoint which will be populated with stats data.
* @return the unique id of the inserted marker
*/
public long insertStatisticsMarker(Location location) {
if (!isRecording()) {
throw new IllegalStateException(
"Unable to insert statistics marker while not recording!");
}
private void buildStatisticsMarker(Waypoint waypoint) {
StringUtils utils = new StringUtils(TrackRecordingService.this);
// Create a new waypoint to save
Waypoint waypoint = new Waypoint();
// Set stop and total time in the stats data
final long time = System.currentTimeMillis();
waypointStatsBuilder.pauseAt(time);
@@ -913,24 +922,18 @@ public class TrackRecordingService extends Service implements LocationListener {
waypoint.setDuration(time - statsBuilder.getStatistics().getStartTime());
// Set the rest of the waypoint data
waypoint.setTrackId(recordingTrackId);
waypoint.setType(Waypoint.TYPE_STATISTICS);
waypoint.setName(TrackRecordingService.this.getString(R.string.statistics));
waypoint.setName(getString(R.string.statistics));
waypoint.setStatistics(waypointStatsBuilder.getStatistics());
waypoint.setDescription(utils.generateWaypointDescription(waypoint));
waypoint.setLocation(location);
waypoint.setIcon(STATISTICS_ICON_URL);
waypoint.setLength(length);
waypoint.setIcon(getString(R.string.stats_icon_url));
waypoint.setStartId(providerUtils.getLastLocationId(recordingTrackId));
Uri uri = providerUtils.insertWaypoint(waypoint);
// Create a new stats keeper for the next marker
// Create a new stats keeper for the next marker.
waypointStatsBuilder = new TripStatisticsBuilder(time);
updateCurrentWaypoint();
return Long.parseLong(uri.getLastPathSegment());
}
private ServiceBinder binder = new ServiceBinder(this);
/**
@@ -961,79 +964,54 @@ public class TrackRecordingService extends Service implements LocationListener {
@Override
public boolean isRecording() {
checkService();
return service.isRecording();
}
private void checkService() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.isRecording();
}
@Override
public long getRecordingTrackId() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
return service.recordingTrackId;
}
@Override
public boolean hasRecorded() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
return service.providerUtils.getLastTrackId() >= 0;
}
@Override
public long startNewTrack() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
return service.startNewTrack();
}
/**
* Insert the given waypoint marker. Users can insert waypoint markers
* to tag locations with a name, description, category etc.
* Inserts a waypoint marker in the track being recorded.
*
* @param waypoint a waypoint
* @return the unique id of the inserted marker
* @param request Details of the waypoint to insert
* @return the unique ID of the inserted marker
*/
@Override
public long insertWaypointMarker(Waypoint waypoint) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.insertWaypointMarker(waypoint);
}
/**
* Insert a statistics marker. A statistics marker holds the stats for
* the last segment up to this marker.
*
* @param location the location where to insert
* @return the unique id of the inserted marker
*/
@Override
public long insertStatisticsMarker(Location location) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.insertStatisticsMarker(location);
public long insertWaypoint(WaypointCreationRequest request) {
checkService();
return service.insertWaypoint(request);
}
@Override
public void endCurrentTrack() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
service.endCurrentTrack();
}
@Override
public void deleteAllTracks() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
if (isRecording()) {
throw new IllegalStateException("Cannot delete all tracks while recording!");
}
@@ -1042,14 +1020,13 @@ public class TrackRecordingService extends Service implements LocationListener {
@Override
public void recordLocation(Location loc) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
checkService();
service.onLocationChanged(loc);
}
@Override
public byte[] getSensorData() {
checkService();
if (service.sensorManager == null) {
Log.d(MyTracksConstants.TAG, "No sensor manager for data.");
return null;
@@ -1063,6 +1040,7 @@ public class TrackRecordingService extends Service implements LocationListener {
@Override
public int getSensorState() {
checkService();
if (service.sensorManager == null) {
Log.d(MyTracksConstants.TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber();
@@ -1095,12 +1073,13 @@ public class TrackRecordingService extends Service implements LocationListener {
providerUtils.updateTrack(track);
statsBuilder = new TripStatisticsBuilder(startTime);
waypointStatsBuilder = new TripStatisticsBuilder(startTime);
currentWaypointId = insertStatisticsMarker(null);
currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
setUpAnnouncer();
length = 0;
showNotification();
registerLocationListener();
splitManager.restore();
sensorManager = SensorManagerFactory.getSensorManager(this);
if (sensorManager != null) {
sensorManager.onStartTrack();
}
@@ -1149,6 +1128,7 @@ public class TrackRecordingService extends Service implements LocationListener {
if (sensorManager != null) {
sensorManager.shutdown();
sensorManager = null;
}
releaseWakeLock();
@@ -0,0 +1,169 @@
/*
* 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.services.sensors;
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
/**
* A sensor manager to connect ANT+ sensors.
* This can include heart rate monitors.
*
* @author Sandor Dornbush
*/
public class AntDirectSensorManager extends AntSensorManager {
/*
* These constants are defined by the ANT+ heart rate monitor spec.
*/
public static final byte HRM_CHANNEL = 0;
public static final byte NETWORK_NUMBER = 1;
public static final byte HEART_RATE_DEVICE_TYPE = 120;
public static final byte POWER_DEVICE_TYPE = 11;
public static final byte MANUFACTURER_ID = 1;
public static final short CHANNEL_PERIOD = 8070;
public static final byte RF_FREQUENCY = 57;
private short deviceNumberHRM;
public AntDirectSensorManager(Context context) {
super(context);
deviceNumberHRM = WILDCARD;
// First read the the device id that we will be pairing with.
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
deviceNumberHRM =
(short) prefs.getInt(context.getString(R.string.ant_heart_rate_sensor_id_key), 0);
}
Log.i(TAG, "Pairing with heart rate monitor: " + deviceNumberHRM);
}
@Override
public void handleMessage(byte[] antMessage) {
int channel =
antMessage[AntMesg.MESG_DATA_OFFSET] & AntDefine.CHANNEL_NUMBER_MASK;
switch (channel) {
case HRM_CHANNEL:
antDecodeHRM(antMessage);
break;
default:
Log.d(TAG, "Unhandled message: " + channel);
}
}
/**
* Decode an ant heart rate monitor message.
* @param antMessage The byte array received from the heart rate monitor.
*/
private void antDecodeHRM(byte[] antMessage) {
// message ID == broadcast
switch (antMessage[AntMesg.MESG_ID_OFFSET]) {
case AntMesg.MESG_BROADCAST_DATA_ID:
handleBroadcastData(antMessage);
break;
case AntMesg.MESG_RESPONSE_EVENT_ID:
handleMessageResponse(antMessage);
break;
case AntMesg.MESG_CHANNEL_ID_ID:
handleChannelId(antMessage);
break;
default:
Log.e(TAG, "Unexpected message id: " + antMessage[3]);
}
}
private void handleBroadcastData(byte[] antMessage) {
if (deviceNumberHRM == WILDCARD) {
getAntReceiver().ANTRequestMessage(HRM_CHANNEL,
AntMesg.MESG_CHANNEL_ID_ID);
Log.d(TAG, "Requesting channel id id.");
}
setSensorState(Sensor.SensorState.CONNECTED);
int bpm = (int) antMessage[10] & 0xFF;
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(bpm)
.setState(Sensor.SensorState.SENDING);
sensorData =
Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis())
.setHeartRate(b)
.build();
}
void handleChannelId(byte[] antMessage) {
// Store the device id.
deviceNumberHRM =
(short) (((int) antMessage[3] & 0xFF |
((int) (antMessage[4] & 0xFF) << 8)) & 0xFFFF);
Log.i(TAG, "Found device id: " + deviceNumberHRM);
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(context.getString(R.string.ant_heart_rate_sensor_id_key), deviceNumberHRM);
editor.commit();
}
private void handleMessageResponse(byte[] antMessage) {
if (antMessage[3] == AntMesg.MESG_EVENT_ID &&
antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel((byte) 0);
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
}
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
super.setupChannel();
antChannelSetup(NETWORK_NUMBER,
HRM_CHANNEL,
deviceNumberHRM,
HEART_RATE_DEVICE_TYPE,
(byte) 0x01,
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
setSensorState(Sensor.SensorState.CONNECTING);
}
public short getDeviceNumberHRM() {
return deviceNumberHRM;
}
void setDeviceNumberHRM(short deviceNumberHRM) {
this.deviceNumberHRM = deviceNumberHRM;
}
}
@@ -0,0 +1,226 @@
/*
* 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.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
/**
* A sensor manager that can connect to an SRM Power Control 7 head unit.
*
* @author Sandor Dornbush
*/
public class AntSRMSensorManager extends AntSensorManager {
/*
* These constants are defined by the SRM Headunit spec.
*/
public static final byte NETWORK_NUMBER = (byte) 0;
public static final byte DEVICE_TYPE = (byte) 12;
public static final byte MANUFACTURER_ID = (byte) 1;
public static final short CHANNEL_PERIOD = (short) 8192;
public static final byte RF_FREQUENCY = (byte) 50;
public static final int MESSAGE_TYPE_INDEX = 3;
public static final int INITIALS_MESSAGE_TYPE = 5;
public static final int SENSOR_DATA_MESSAGE_TYPE = 6;
public static final int MESSAGE_ID_INDEX = 4;
/**
* This can be any value between 0..8
*/
private byte channel = (byte) 5;
/**
* This is the id of this device.
* The head unit must be programmed to expect this number.
*/
private byte deviceId;
/**
* The id of the last message received from the SRM head unit.
*/
private int lastMessageId;
public AntSRMSensorManager(Context context) {
super(context);
// First read the the device id that we will be announcing.
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
deviceId = (byte) prefs.getInt(
context.getString(R.string.ant_srm_bridge_sensor_id_key),
WILDCARD);
}
}
@Override
public void handleMessage(byte[] antMessage) {
// Parse channel number
byte recievedChannel = (byte) (antMessage[AntMesg.MESG_DATA_OFFSET]
& AntDefine.CHANNEL_NUMBER_MASK);
if (recievedChannel != channel) {
Log.d(MyTracksConstants.TAG, "Unexpected channel: " + recievedChannel);
return;
}
switch (antMessage[AntMesg.MESG_ID_OFFSET]) {
case AntMesg.MESG_BROADCAST_DATA_ID:
handleBroadcastData(antMessage);
break;
case AntMesg.MESG_RESPONSE_EVENT_ID:
handleMessageResponse(antMessage);
break;
case AntMesg.MESG_CHANNEL_ID_ID:
handleChannelId(antMessage);
break;
default:
Log.e(MyTracksConstants.TAG,
"Unexpected message id: " + antMessage[3]);
}
}
private void handleBroadcastData(byte[] antMessage) {
if (deviceId == WILDCARD) {
getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID);
Log.d(MyTracksConstants.TAG, "Requesting channel id id.");
}
setSensorState(Sensor.SensorState.CONNECTED);
switch(antMessage[MESSAGE_TYPE_INDEX]) {
case INITIALS_MESSAGE_TYPE:
// TODO handle initials message.
break;
case SENSOR_DATA_MESSAGE_TYPE:
parseSensorData(antMessage);
break;
default:
Log.e(MyTracksConstants.TAG,
"Unexpected message type: " + antMessage[MESSAGE_TYPE_INDEX]);
}
}
private void handleChannelId(byte[] antMessage) {
// Store the device id.
deviceId = antMessage[3];
Log.i(MyTracksConstants.TAG, "Found device id: " + deviceId);
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(context.getString(R.string.ant_srm_bridge_sensor_id_key), deviceId);
editor.commit();
}
private void handleMessageResponse(byte[] antMessage) {
if (antMessage[3] == AntMesg.MESG_EVENT_ID
&& antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(MyTracksConstants.TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel(channel);
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(MyTracksConstants.TAG,
"Disconnected from the sensor: " + getSensorState());
}
}
private void parseSensorData(byte[] antMessage) {
if (antMessage.length != 11) {
Log.e(MyTracksConstants.TAG,
"Unexpected ant message length: " + antMessage.length);
return;
}
int newMessageId = antMessage[MESSAGE_ID_INDEX] & 0xFF;
if (lastMessageId == newMessageId) {
// Repeated message.
Log.i(MyTracksConstants.TAG,
String.format("SRM ignoring repeat: 0x%X", newMessageId));
return;
}
if (newMessageId < lastMessageId) {
if (!(newMessageId < 20 && lastMessageId > 200)) {
Log.i(MyTracksConstants.TAG,
String.format("SRM ignoring repeat: 0x%X", newMessageId));
return;
} // else assume the byte overflowed to 0.
}
lastMessageId = newMessageId;
Sensor.SensorDataSet.Builder builder = Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis());
int power = SensorUtils.unsignedShortToInt(antMessage, 5);
if (power >= 0) {
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(power)
.setState(Sensor.SensorState.SENDING);
builder.setPower(b);
}
int speed = SensorUtils.unsignedShortToInt(antMessage, 7);
if (speed >= 0) {
// The speed from srm is in 1/10 km/hr.
// Sensor data expects the speed as m/s.
// TODO finish this.
//sensorData.setWheelSpeed(UnitConversions.TENTH_KMH_TO_MPS * speed);
}
int cadence = antMessage[9] & 0xFF;
if (cadence >= 0) {
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(cadence)
.setState(Sensor.SensorState.SENDING);
builder.setCadence(b);
}
int bpm = antMessage[10] & 0xFF;
if (bpm > 0) {
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(bpm)
.setState(Sensor.SensorState.SENDING);
builder.setHeartRate(b);
}
sensorData = builder.build();
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
antChannelSetup(NETWORK_NUMBER,
channel,
deviceId,
DEVICE_TYPE,
MANUFACTURER_ID,
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
lastMessageId = 0;
setSensorState(Sensor.SensorState.CONNECTING);
}
}
@@ -0,0 +1,228 @@
/*
* 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.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* An abstract class for ANT+ based sensors.
*
* @author Sandor Dornbush
*/
public abstract class AntSensorManager extends SensorManager {
// ANT Service
public static final int EVENT_ANT_ENABLED = 1;
public static final int EVENT_ANT_DISABLED = 2;
// Pairing
protected static final short WILDCARD = 0;
private AntInterface antReceiver;
private boolean status;
// Flag to know if the ANT App was interrupted
// TODO: This code path is not used but probably should be.
private boolean antInterrupted;
/**
* The data from the sensors.
*/
protected SensorDataSet sensorData;
protected Context context;
private static final boolean DEBUGGING = false;
/**
* Receives all of the ANT intents and dispatches to the proper handler.
*/
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String antAction = intent.getAction();
Log.i(MyTracksConstants.TAG, "enter onReceive" + antAction);
if (antAction.equals(AntInterfaceIntent.ANT_ENABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_ENABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_ENABLED, 0));
}
if (antAction.equals(AntInterfaceIntent.ANT_DISABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_DISABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_DISABLED, 0));
}
if (antAction.equals(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION)) {
byte[] antMessage =
intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
if (DEBUGGING) {
StringBuilder sb =
new StringBuilder("ANT_RX_MESSAGE_ACTION Rx:");
for (int i = 0; i < antMessage.length; i++)
sb.append(String.format("[%X]]", (antMessage[i] & 0xFF)));
Log.d(MyTracksConstants.TAG, sb.toString());
}
handleMessage(antMessage);
}
}
};
/**
* Handler for all the ANT related events. The events will be handled only
* when we are in the ANT application main menu
**/
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_ANT_ENABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_ENABLED");
break;
case EVENT_ANT_DISABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_DISABLED");
break;
}
}
};
public AntSensorManager(Context context) {
this.context = context;
// Need to enable the ANT if it is not enabled earlier
antReceiver = AntInterface.getInstance(context, null);
if (antReceiver == null) {
return;
}
status = antReceiver.isEnabled();
if (!status) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
status = antReceiver.enable();
if (!status) {
Log.e(MyTracksConstants.TAG, "Can not enable ANT interface");
} else {
Log.i(MyTracksConstants.TAG, "Powering on Radio");
}
} else {
Log.i(MyTracksConstants.TAG, "Radio already enabled");
}
}
}
@Override
public void setupChannel() {
Log.i(MyTracksConstants.TAG, "Registering for ant intents.");
// Register for ANT intent broadcasts.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(receiver, intentFilter);
}
@Override
public SensorDataSet getSensorDataSet() {
return sensorData;
}
@Override
public boolean isEnabled() {
return true;
}
public abstract void handleMessage(byte[] antMessage);
@Override
public void onDestroy() {
Log.i(MyTracksConstants.TAG, "Unregistering for ant intents.");
context.unregisterReceiver(receiver);
}
/**
* Set up an ANT+ channel.
*
* @return True on success.
*/
protected boolean antChannelSetup(byte networkNumber, byte channelNumber,
short deviceNumber, byte deviceType, byte txType, short channelPeriod,
byte radioFreq, byte proxSearch) {
// Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 =
// ANTFS)
if (!antReceiver.ANTAssignChannel(channelNumber,
AntDefine.PARAMETER_RX_NOT_TX, networkNumber)) {
return false;
}
if (!antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType,
txType)) {
return false;
}
if (!antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod)) {
return false;
}
if (!antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq)) {
return false;
}
// Disable high priority search
if (!antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0)) {
return false;
}
// Set search timeout to 30 seconds (low priority search))
if (!antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12)) {
return false;
}
if (deviceNumber == WILDCARD) {
// Configure proximity search, if using wild card search
if (!antReceiver.ANTSetProximitySearch(channelNumber, proxSearch)) {
return false;
}
}
return !antReceiver.ANTOpenChannel(channelNumber);
}
public AntInterface getAntReceiver() {
return antReceiver;
}
}
@@ -13,7 +13,6 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
@@ -46,15 +45,19 @@ public class SensorManagerFactory {
return null;
}
String sensor = prefs.getString(context.getString(R.string.sensor_type_key),
null);
String sensor = prefs.getString(context.getString(R.string.sensor_type_key), null);
Log.i(MyTracksConstants.TAG, "Creating sensor of type: " + sensor);
if (sensor == null) {
return null;
} else if (sensor.equals(context.getString(R.string.ant_sensor_type))) {
return new AntDirectSensorManager(context);
} else if (sensor.equals(context.getString(R.string.srm_ant_bridge_sensor_type))) {
return new AntSRMSensorManager(context);
} else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) {
return new ZephyrSensorManager(context);
} else {
Log.w(MyTracksConstants.TAG, "Unable to find sensor type: " + sensor);
return null;
}
}
@@ -35,8 +35,9 @@ public class ZephyrMessageParser implements MessageParser {
}
Log.w(MyTracksConstants.TAG, "Got zephyr data: " + sb);
// The provided units are 1/16 strides per minute.
int cadence = SensorUtils.unsignedShortToInt(buffer, 56);
// Heart Rate
// TODO: Fix the cadence calculation.
// int cadence = SensorUtils.unsignedShortToInt(buffer, 56);
// Heart Rate
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(buffer[12] & 0xFF)
.setState(Sensor.SensorState.SENDING);
@@ -62,24 +62,18 @@ public class ApiFeatures {
* Allow subclasses for mocking, but no direct instantiation.
*/
protected ApiFeatures() {
if (getApiLevel() >= 5) {
try {
Class<?> clazz = Class.forName(
"com.google.android.apps.mytracks.util.EclairPlatformAdapter");
apiPlatformAdapter = (ApiPlatformAdapter) clazz.newInstance();
} catch (Exception e) {
Log.i(MyTracksConstants.TAG, "ApiFeatures: Unable to instantiate Eclair"
+ " platform adapter", e);
}
}
if (apiPlatformAdapter == null) {
if (getApiLevel() >= 9) {
apiPlatformAdapter = new GingerbreadPlatformAdapter();
} else if (getApiLevel() >= 5) {
apiPlatformAdapter = new EclairPlatformAdapter();
} else {
Log.i(MyTracksConstants.TAG,
"ApiFeatures: Using default platform adapter");
// Cupcake adapter is always supported, so it's safe to do static linkage.
apiPlatformAdapter = new CupcakePlatformAdapter();
}
}
public ApiPlatformAdapter getApiPlatformAdapter() {
return apiPlatformAdapter;
}
@@ -107,7 +101,11 @@ public class ApiFeatures {
return true;
}
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
// Visible for testing.
protected int getApiLevel() {
return ANDROID_API_LEVEL;
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences;
import android.os.HandlerThread;
/**
@@ -57,4 +58,15 @@ public interface ApiPlatformAdapter {
* @return true whether the handler has been stopped or not.
*/
boolean stopHandlerThread(HandlerThread handlerThread);
/**
* Applies all changes done to the given preferences editor.
* Changes may or may not be applied immediately.
*/
void applyPreferenceChanges(SharedPreferences.Editor editor);
/**
* Enables strict mode where supported, only if this is a development build.
*/
void enableStrictMode();
}
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
import android.os.HandlerThread;
/**
@@ -50,4 +51,14 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
// Do nothing, as Cupcake doesn't provide quit().
return false;
}
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
}
@Override
public void enableStrictMode() {
// Not supported
}
}
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
import android.os.HandlerThread;
/**
@@ -45,4 +46,14 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
public boolean stopHandlerThread(HandlerThread handlerThread) {
return handlerThread.quit();
}
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
}
@Override
public void enableStrictMode() {
// Not supported
}
}
@@ -0,0 +1,36 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.MyTracksConstants;
import android.content.SharedPreferences.Editor;
import android.os.StrictMode;
import android.util.Log;
/**
* The Gingerbread (API level 9) specific implememntation of the
* {@link ApiPlatformAdapter}.
*
* @author Rodrigo Damazio
*/
public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
@Override
public void applyPreferenceChanges(Editor editor) {
// Apply asynchronously
editor.apply();
}
@Override
public void enableStrictMode() {
Log.d(MyTracksConstants.TAG, "Enabling strict mode");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyDeath()
.build());
}
}
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.location.Location;
import android.util.Log;
@@ -38,6 +39,7 @@ import java.util.Stack;
* @author Leif Hendrik Wilden
*/
public class MyTracksUtils {
private static final int RELEASE_SIGNATURE_HASHCODE = -1855564782;
/**
* Computes the distance on the two sphere between the point c0 and the line
@@ -250,6 +252,24 @@ public class MyTracksUtils {
(int) (location.getLongitude() * 1E6));
}
/**
* Returns whether or not this is a release build.
*/
public static boolean isRelease(Context context) {
try {
Signature [] sigs = context.getPackageManager().getPackageInfo(
context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
for (Signature sig : sigs) {
if (sig.hashCode() == RELEASE_SIGNATURE_HASHCODE) {
return true;
}
}
} catch (NameNotFoundException e) {
Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
}
return false;
}
/**
* Get the My Tracks version from the manifest.
*
@@ -258,17 +278,17 @@ public class MyTracksUtils {
public static String getMyTracksVersion(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
"com.google.android.maps.mytracks",
"com.google.android.maps.mytracks",
PackageManager.GET_META_DATA);
return pi.versionName;
} catch (NameNotFoundException e) {
} catch (NameNotFoundException e) {
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
return "";
}
}
/**
* This is a utility class w/ only static memebers.
* This is a utility class w/ only static members.
*/
protected MyTracksUtils() {
}
+1 -1
View File
@@ -8,5 +8,5 @@
# project structure.
# Project target.
target=android-8
target=android-9
android.library=true
@@ -21,6 +21,7 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import java.util.Iterator;
import java.util.List;
/**
@@ -179,8 +180,9 @@ public interface MyTracksProviderUtils {
/**
* 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.
* Note that the returned track object does not have any track points attached.
* Use {@link #getLocationIterator(long, long, boolean, LocationFactory)} to load
* the track points.
*
* @param id desired unique track id
* @return a Track object, or null if not found
@@ -188,24 +190,14 @@ public interface MyTracksProviderUtils {
Track getTrack(long id);
/**
* Retrieves all tracks without track points. If no tracks exist an empty
* list will be returned. Use {@link #getTrackPoints(Track, int)} to load
* the track points.
* Retrieves all tracks without track points. If no tracks exist, an empty
* list will be returned. Use {@link #getLocationIterator(long, long, boolean, LocationFactory)}
* to load the track points.
*
* @return a list of all the recorded tracks
*/
List<Track> getAllTracks();
/**
* 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);
/**
* Creates a cursor over the tracks provider with a given selection.
* Caller gets to own the returned cursor. Don't forget to close it.
@@ -309,6 +301,87 @@ public interface MyTracksProviderUtils {
* @return a new waypoint object
*/
Waypoint createWaypoint(Cursor cursor);
/**
* A lightweight wrapper around the original {@link Cursor} with a method to clean up.
*/
interface LocationIterator extends Iterator<Location> {
/**
* Returns ID of the most recently retrieved track point through a call to {@link #next()}.
*
* @return the ID of the most recent track point ID.
*/
long getLocationId();
/**
* Should be called in case the underlying iterator hasn't reached the last record.
*/
void close();
}
/**
* A factory for creating new {@class Location}s.
*/
interface LocationFactory {
/**
* Creates a new {@link Location} object to be populated from the underlying database record.
* It's up to the implementing class to decide whether to create a new instance or reuse
* existing to optimize for speed.
*
* @return a {@link Location} to be populated from the database.
*/
Location createLocation();
}
/**
* The default {@class Location}s factory, which creates a new location of 'gps' type.
*/
LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() {
@Override
public Location createLocation() {
return new Location("gps");
}
};
/**
* Creates a new read-only iterator over all track points for the given track. It provides
* a lightweight way of iterating over long tracks without failing due to the underlying cursor
* limitations. Since it's a read-only iterator, {@link Iterator#remove()} always throws
* {@class UnsupportedOperationException}.
*
* Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so,
* the iterator calls {@link LocationFactory#createLocation()} and populates it with information
* retrieved from the record.
*
* When done with iteration, you must call {@link LocationIterator#close()} to make sure that all
* resources are properly deallocated.
*
* Example use:
* <code>
* ...
* LocationIterator it = providerUtils.getLocationIterator(
* 1, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
* try {
* for (Location loc : it) {
* ... // Do something useful with the location.
* }
* } finally {
* it.close();
* }
* ...
* </code>
*
* @param trackId the ID of a track to retrieve locations for.
* @param startTrackPointId the ID of the first track point to load, or -1 to start from
* the first point.
* @param descending if true the results will be returned in descending ID
* order (latest location first).
* @param locationFactory the factory for creating new locations.
*
* @return the read-only iterator over the given track's points.
*/
LocationIterator getLocationIterator(long trackId, long startTrackPointId, boolean descending,
LocationFactory locationFactory);
/**
* A factory which can produce instances of {@link MyTracksProviderUtils},
@@ -15,7 +15,7 @@
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.*;
import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -29,6 +29,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Helper class providing easy access to locations and tracks in the
@@ -39,6 +40,8 @@ import java.util.List;
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private final ContentResolver contentResolver;
private int defaultCursorBatchSize = 2000;
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
@@ -125,7 +128,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private static ContentValues createContentValues(Waypoint waypoint) {
ContentValues values = new ContentValues();
TripStatistics stats = waypoint.getStatistics();
// Values id < 0 indicate no id is available:
if (waypoint.getId() >= 0) {
@@ -139,21 +141,24 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values.put(WaypointsColumns.TYPE, waypoint.getType());
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
values.put(WaypointsColumns.STARTTIME, stats.getStartTime());
values.put(WaypointsColumns.STARTID, waypoint.getStartId());
values.put(WaypointsColumns.STOPID, waypoint.getStopId());
values.put(WaypointsColumns.TOTALDISTANCE, stats.getTotalDistance());
values.put(WaypointsColumns.TOTALTIME, stats.getTotalTime());
values.put(WaypointsColumns.MOVINGTIME, stats.getMovingTime());
values.put(WaypointsColumns.AVGSPEED, stats.getAverageSpeed());
values.put(WaypointsColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
values.put(WaypointsColumns.MAXSPEED, stats.getMaxSpeed());
values.put(WaypointsColumns.MINELEVATION, stats.getMinElevation());
values.put(WaypointsColumns.MAXELEVATION, stats.getMaxElevation());
values.put(WaypointsColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, stats.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, stats.getMaxGrade());
TripStatistics stats = waypoint.getStatistics();
if (stats != null) {
values.put(WaypointsColumns.TOTALDISTANCE, stats.getTotalDistance());
values.put(WaypointsColumns.TOTALTIME, stats.getTotalTime());
values.put(WaypointsColumns.MOVINGTIME, stats.getMovingTime());
values.put(WaypointsColumns.AVGSPEED, stats.getAverageSpeed());
values.put(WaypointsColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
values.put(WaypointsColumns.MAXSPEED, stats.getMaxSpeed());
values.put(WaypointsColumns.MINELEVATION, stats.getMinElevation());
values.put(WaypointsColumns.MAXELEVATION, stats.getMaxElevation());
values.put(WaypointsColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, stats.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, stats.getMaxGrade());
values.put(WaypointsColumns.STARTTIME, stats.getStartTime());
}
Location location = waypoint.getLocation();
if (location != null) {
@@ -185,55 +190,78 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
fillLocation(cursor, location);
return location;
}
/**
* A cache of track column indices.
*/
private static class CachedTrackColumnIndices {
public final int idxId;
public final int idxLatitude;
public final int idxLongitude;
public final int idxAltitude;
public final int idxTime;
public final int idxBearing;
public final int idxAccuracy;
public final int idxSpeed;
public final int idxSensor;
@Override
public void fillLocation(Cursor cursor, Location location) {
public CachedTrackColumnIndices(Cursor cursor) {
idxId = cursor.getColumnIndex(TrackPointsColumns._ID);
idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
idxLongitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
idxAltitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
idxTime = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
}
}
private void fillLocation(Cursor cursor, CachedTrackColumnIndices columnIndices,
Location location) {
location.reset();
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);
int idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
if (!cursor.isNull(idxLatitude)) {
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
if (!cursor.isNull(columnIndices.idxLatitude)) {
location.setLatitude(1. * cursor.getInt(columnIndices.idxLatitude) / 1E6);
}
if (!cursor.isNull(idxLongitude)) {
location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6);
if (!cursor.isNull(columnIndices.idxLongitude)) {
location.setLongitude(1. * cursor.getInt(columnIndices.idxLongitude) / 1E6);
}
if (!cursor.isNull(idxAltitude)) {
location.setAltitude(cursor.getFloat(idxAltitude));
if (!cursor.isNull(columnIndices.idxAltitude)) {
location.setAltitude(cursor.getFloat(columnIndices.idxAltitude));
}
if (!cursor.isNull(idxTime)) {
location.setTime(cursor.getLong(idxTime));
if (!cursor.isNull(columnIndices.idxTime)) {
location.setTime(cursor.getLong(columnIndices.idxTime));
}
if (!cursor.isNull(idxBearing)) {
location.setBearing(cursor.getFloat(idxBearing));
if (!cursor.isNull(columnIndices.idxBearing)) {
location.setBearing(cursor.getFloat(columnIndices.idxBearing));
}
if (!cursor.isNull(idxSpeed)) {
location.setSpeed(cursor.getFloat(idxSpeed));
if (!cursor.isNull(columnIndices.idxSpeed)) {
location.setSpeed(cursor.getFloat(columnIndices.idxSpeed));
}
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
if (!cursor.isNull(columnIndices.idxAccuracy)) {
location.setAccuracy(cursor.getFloat(columnIndices.idxAccuracy));
}
if (location instanceof MyTracksLocation &&
!cursor.isNull(idxSensor)) {
!cursor.isNull(columnIndices.idxSensor)) {
MyTracksLocation mtLocation = (MyTracksLocation) location;
// TODO get the right buffer.
Sensor.SensorDataSet sensorData;
try {
sensorData = Sensor.SensorDataSet.parseFrom(cursor.getBlob(idxSensor));
sensorData = Sensor.SensorDataSet.parseFrom(cursor.getBlob(columnIndices.idxSensor));
mtLocation.setSensorData(sensorData);
} catch (InvalidProtocolBufferException e) {
Log.w(TAG, "Failed to parse sensor data.", e);
}
}
}
@Override
public void fillLocation(Cursor cursor, Location location) {
CachedTrackColumnIndices columnIndicies = new CachedTrackColumnIndices(cursor);
fillLocation(cursor, columnIndicies, location);
}
@Override
public Track createTrack(Cursor cursor) {
@@ -383,7 +411,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int idxSpeed = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
Waypoint waypoint = new Waypoint();
TripStatistics stats = waypoint.getStatistics();
if (!cursor.isNull(idxId)) {
waypoint.setId(cursor.getLong(idxId));
@@ -412,41 +439,57 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxDuration)) {
waypoint.setDuration(cursor.getLong(idxDuration));
}
if (!cursor.isNull(idxStartTime)) {
stats.setStartTime(cursor.getLong(idxStartTime));
}
if (!cursor.isNull(idxStartId)) {
waypoint.setStartId(cursor.getLong(idxStartId));
}
if (!cursor.isNull(idxStopId)) {
waypoint.setStopId(cursor.getLong(idxStopId));
}
TripStatistics stats = new TripStatistics();
boolean hasStats = false;
if (!cursor.isNull(idxStartTime)) {
stats.setStartTime(cursor.getLong(idxStartTime));
hasStats = true;
}
if (!cursor.isNull(idxTotalDistance)) {
stats.setTotalDistance(cursor.getFloat(idxTotalDistance));
hasStats = true;
}
if (!cursor.isNull(idxTotalTime)) {
stats.setTotalTime(cursor.getLong(idxTotalTime));
hasStats = true;
}
if (!cursor.isNull(idxMovingTime)) {
stats.setMovingTime(cursor.getLong(idxMovingTime));
hasStats = true;
}
if (!cursor.isNull(idxMaxSpeed)) {
stats.setMaxSpeed(cursor.getFloat(idxMaxSpeed));
hasStats = true;
}
if (!cursor.isNull(idxMinElevation)) {
stats.setMinElevation(cursor.getFloat(idxMinElevation));
hasStats = true;
}
if (!cursor.isNull(idxMaxElevation)) {
stats.setMaxElevation(cursor.getFloat(idxMaxElevation));
hasStats = true;
}
if (!cursor.isNull(idxElevationGain)) {
stats.setTotalElevationGain(cursor.getFloat(idxElevationGain));
hasStats = true;
}
if (!cursor.isNull(idxMinGrade)) {
stats.setMinGrade(cursor.getFloat(idxMinGrade));
hasStats = true;
}
if (!cursor.isNull(idxMaxGrade)) {
stats.setMaxGrade(cursor.getFloat(idxMaxGrade));
hasStats = true;
}
if (hasStats) {
waypoint.setStatistics(stats);
}
Location location = new Location("");
@@ -780,13 +823,12 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
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);
if (minTrackPointId >= 0) {
selection = String.format("%s=%d AND %s%s%d",
TrackPointsColumns.TRACKID, trackId, TrackPointsColumns._ID,
descending ? "<=" : ">=", minTrackPointId);
} else {
selection = String.format("%s=%d",
TrackPointsColumns.TRACKID, trackId);
selection = String.format("%s=%d", TrackPointsColumns.TRACKID, trackId);
}
String sortOrder = "_id " + (descending ? "DESC" : "ASC");
@@ -794,8 +836,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxLocations;
}
return contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
return contentResolver.query(TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@Override
@@ -825,49 +866,24 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
String select = TracksColumns._ID + "=" + id;
return findTrackBy(select);
}
@Override
public List<Track> getAllTracks() {
Cursor cursor = getTracksCursor(null);
if (cursor == null || !cursor.moveToFirst()) {
return new ArrayList<Track>();
}
ArrayList<Track> tracks = new ArrayList<Track>();
if (cursor != null) {
tracks.ensureCapacity(cursor.getCount());
List<Track> tracks = new ArrayList<Track>(cursor.getCount());
do {
tracks.add(createTrack(cursor));
} while(cursor.moveToNext());
return tracks;
}
@Override
public long getTrackPoints(Track track, int maxPoints) {
long lastId = -1;
Cursor cursor = getLocationsCursor(track.getId(), -1, maxPoints, true);
if (cursor == null) {
Log.w(TAG, "Cannot get a locations cursor!");
return lastId;
}
try {
final int idColumnIdx =
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
if (cursor.moveToLast()) {
if (cursor.moveToFirst()) {
do {
Location location = createLocation(cursor);
if (location == null) {
continue;
}
track.addLocation(location);
lastId = cursor.getLong(idColumnIdx);
} while (cursor.moveToPrevious());
tracks.add(createTrack(cursor));
} while(cursor.moveToNext());
}
} catch (RuntimeException e) {
Log.w(TAG, "Caught unexpected exception.", e);
} finally {
cursor.close();
}
return lastId;
return tracks;
}
@Override
@@ -939,4 +955,85 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
contentResolver.update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
@Override
public LocationIterator getLocationIterator(final long trackId, final long startTrackPointId,
final boolean descending, final LocationFactory locationFactory) {
if (locationFactory == null) {
throw new IllegalArgumentException("Expecting non-null locationFactory");
}
return new LocationIterator() {
private long lastTrackPointId = startTrackPointId;
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);
}
private boolean advanceCursorToNextBatch() {
long pointId = lastTrackPointId + (descending ? -1 : 1);
Log.d(TAG, "Advancing cursor point ID: " + pointId);
cursor.close();
cursor = getCursor(pointId);
return cursor != null;
}
@Override
public long getLocationId() {
return lastTrackPointId;
}
@Override
public boolean hasNext() {
if (cursor == null) {
return false;
}
if (cursor.isAfterLast()) {
return false;
}
if (cursor.isLast()) {
// If the current batch size was less that max, we can safely return, otherwise
// we need to advance to the next batch.
return cursor.getCount() == defaultCursorBatchSize &&
advanceCursorToNextBatch() && !cursor.isAfterLast();
}
return true;
}
@Override
public Location next() {
if (cursor == null ||
!(cursor.moveToNext() || advanceCursorToNextBatch() || cursor.moveToNext())) {
throw new NoSuchElementException();
}
lastTrackPointId = cursor.getLong(columnIndices.idxId);
Location location = locationFactory.createLocation();
fillLocation(cursor, columnIndices, location);
return location;
}
@Override
public void close() {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
// @VisibleForTesting
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
this.defaultCursorBatchSize = defaultCursorBatchSize;
}
}
@@ -85,7 +85,7 @@ public final class Waypoint implements Parcelable {
/** Stop track point id */
private long stopId = -1;
private TripStatistics stats = new TripStatistics();
private TripStatistics stats;
/** The length of the track, without smoothing. */
private double length;
@@ -0,0 +1,2 @@
package com.google.android.apps.mytracks.content;
parcelable WaypointCreationRequest;
@@ -0,0 +1,105 @@
/*
* 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.os.Parcel;
import android.os.Parcelable;
/**
* A request for the service to create a waypoint at the current location.
*
* @author Sandor Dornbush
*/
public class WaypointCreationRequest implements Parcelable {
public static enum WaypointType {
MARKER,
STATISTICS;
}
private WaypointType type;
private String name;
private String description;
private String iconUrl;
public final static WaypointCreationRequest DEFAULT_MARKER =
new WaypointCreationRequest(WaypointType.MARKER);
public final static WaypointCreationRequest DEFAULT_STATISTICS =
new WaypointCreationRequest(WaypointType.STATISTICS);
private WaypointCreationRequest(WaypointType type) {
this.type = type;
}
public WaypointCreationRequest(WaypointType type, String name,
String description, String iconUrl) {
this.type = type;
this.name = name;
this.description = description;
this.iconUrl = iconUrl;
}
public static class Creator implements Parcelable.Creator<WaypointCreationRequest> {
@Override
public WaypointCreationRequest createFromParcel(Parcel source) {
int i = source.readInt();
if (i > WaypointType.values().length) {
throw new IllegalArgumentException("Could not find waypoint type: " + i);
}
WaypointCreationRequest request = new WaypointCreationRequest(WaypointType.values()[i]);
request.description = source.readString();
request.iconUrl = source.readString();
request.name = source.readString();
return request;
}
public WaypointCreationRequest[] newArray(int size) {
return new WaypointCreationRequest[size];
}
}
public static final Creator CREATOR = new Creator();
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int arg1) {
parcel.writeInt(type.ordinal());
parcel.writeString(description);
parcel.writeString(iconUrl);
parcel.writeString(name);
}
public WaypointType getType() {
return type;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getIconUrl() {
return iconUrl;
}
}
@@ -15,7 +15,7 @@
*/
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
/**
* MyTracks service.
@@ -45,18 +45,10 @@ interface ITrackRecordingService {
/**
* Inserts a waypoint marker in the track being recorded.
*
* @param waypoint the waypoint to insert
* @param request Details for the waypoint to be inserted.
* @return the unique ID of the inserted marker
*/
long insertWaypointMarker(in Waypoint waypoint);
/**
* Inserts a statistics marker in the track being recorded.
*
* @param location the location at which to insert the marker
* @return the unique ID of the inserted marker
*/
long insertStatisticsMarker(in Location location);
long insertWaypoint(in WaypointCreationRequest request);
/**
* Inserts a location in the track being recorded.
+1 -1
View File
@@ -10,4 +10,4 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=Google Inc.:Google APIs:8
target=Google Inc.:Google APIs:9
@@ -15,9 +15,9 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.ChartValueSeries.ZoomSettings;
import com.google.android.maps.mytracks.R;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.test.AndroidTestCase;
@@ -26,29 +26,24 @@ import android.test.AndroidTestCase;
*/
public class ChartValueSeriesTest extends AndroidTestCase {
private ChartValueSeries series;
private Paint fillPaint1;
@Override
protected void setUp() throws Exception {
fillPaint1 = new Paint();
fillPaint1.setStyle(Style.FILL);
fillPaint1.setColor(getContext().getResources().getColor(R.color.green));
fillPaint1.setAntiAlias(true);
series = new ChartValueSeries(getContext(),
"###,###",
fillPaint1,
null,
100,
R.color.elevation_fill,
R.color.elevation_border,
new ZoomSettings(5, new int[] {100}),
R.string.elevation);
}
public void testInitialConditions() {
assertEquals(25, series.getInterval());
assertEquals(0, series.getInterval());
assertEquals(1, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(0, series.getMax());
assertEquals(0.0, series.getSpread());
assertEquals(fillPaint1, series.getPaint());
assertEquals(Style.STROKE, series.getPaint().getStyle());
assertEquals(getContext().getString(R.string.elevation),
series.getTitle());
assertTrue(series.isEnabled());
@@ -63,7 +58,7 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(0);
series.update(10);
series.updateDimension();
assertEquals(25, series.getInterval());
assertEquals(100, series.getInterval());
assertEquals(3, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(100, series.getMax());
@@ -74,7 +69,7 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(0);
series.update(901);
series.updateDimension();
assertEquals(200, series.getInterval());
assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(0, series.getMin());
assertEquals(1000, series.getMax());
@@ -85,10 +80,60 @@ public class ChartValueSeriesTest extends AndroidTestCase {
series.update(500);
series.update(1401);
series.updateDimension();
assertEquals(200, series.getInterval());
assertEquals(100, series.getInterval());
assertEquals(5, series.getMaxLabelLength());
assertEquals(500, series.getMin());
assertEquals(1500, series.getMax());
assertEquals(1000.0, series.getSpread());
}
public void testZoomSettings_invalidArgs() {
try {
new ZoomSettings(0, new int[] {10, 50, 100});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, null);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, new int[] {});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
try {
new ZoomSettings(1, new int[] {1, 3, 2});
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
// OK.
}
}
public void testZoomSettings_minAligned() {
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
assertEquals(10, settings.calculateInterval(0, 15));
assertEquals(10, settings.calculateInterval(0, 50));
assertEquals(50, settings.calculateInterval(0, 111));
assertEquals(50, settings.calculateInterval(0, 250));
assertEquals(100, settings.calculateInterval(0, 251));
assertEquals(100, settings.calculateInterval(0, 10000));
}
public void testZoomSettings_minNotAligned() {
ZoomSettings settings = new ZoomSettings(5, new int[] {10, 50, 100});
assertEquals(50, settings.calculateInterval(5, 55));
assertEquals(10, settings.calculateInterval(10, 60));
assertEquals(50, settings.calculateInterval(7, 250));
assertEquals(100, settings.calculateInterval(7, 257));
assertEquals(100, settings.calculateInterval(11, 10000));
// A regression test.
settings = new ZoomSettings(5, new int[] {5, 10, 20});
assertEquals(10, settings.calculateInterval(-37.14, -11.89));
}
}
@@ -0,0 +1,171 @@
/*
* 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 com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
import android.content.Context;
import android.location.Location;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A unit test for {@link MyTracksProviderUtilsImpl}.
*
* @author Bartlomiej Niechwiej
*/
public class MyTracksProviderUtilsImplTest extends AndroidTestCase {
private Context context;
private MyTracksProviderUtils providerUtils;
@Override
protected void setUp() throws Exception {
super.setUp();
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
context = new MockContext(mockContentResolver, targetContext);
MyTracksProvider provider = new MyTracksProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
setContext(context);
providerUtils = MyTracksProviderUtils.Factory.get(context);
}
public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
public void testLocationIterator_customFactory() {
final Location location = new Location("test_location");
final AtomicInteger counter = new AtomicInteger();
testIterator(1, 15, 4, false, new LocationFactory() {
@Override
public Location createLocation() {
counter.incrementAndGet();
return location;
}
});
// Make sure we were called exactly as many times as we had track points.
assertEquals(15, counter.get());
}
public void testLocationIterator_nullFactory() {
try {
testIterator(1, 15, 4, false, null);
fail("Expecting IllegalArgumentException");
} catch (IllegalArgumentException e) {
// Expected.
}
}
public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 50, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
testIterator(2, 50, 25, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
}
private List<Location> testIterator(long trackId, int numPoints, int batchSize,
boolean descending, LocationFactory locationFactory) {
long lastPointId = initializeTrack(trackId, numPoints);
((MyTracksProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
List<Location> locations = new ArrayList<Location>(numPoints);
LocationIterator it = providerUtils.getLocationIterator(trackId, -1, descending, locationFactory);
try {
while (it.hasNext()) {
Location loc = it.next();
assertNotNull(loc);
locations.add(loc);
// Make sure the IDs are returned in the right order.
assertEquals(descending ? lastPointId - locations.size() + 1
: lastPointId - numPoints + locations.size(), it.getLocationId());
}
assertEquals(numPoints, locations.size());
} finally {
it.close();
}
return locations;
}
private long initializeTrack(long id, int numPoints) {
Track track = new Track();
track.setId(id);
track.setName("Test: " + id);
track.setNumberOfPoints(numPoints);
providerUtils.insertTrack(track);
track = providerUtils.getTrack(id);
assertNotNull(track);
Location[] locations = new Location[numPoints];
for (int i = 0; i < numPoints; ++i) {
Location loc = new Location("test");
loc.setLatitude(37.0 + (double) i / 10000.0);
loc.setLongitude(57.0 - (double) i / 10000.0);
loc.setAccuracy((float) i / 100.0f);
loc.setAltitude(i * 2.5);
locations[i] = loc;
}
providerUtils.bulkInsertTrackPoints(locations, numPoints, id);
// Load all inserted locations.
long lastPointId = -1;
int counter = 0;
LocationIterator it = providerUtils.getLocationIterator(id, -1, false,
MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
try {
while (it.hasNext()) {
it.next();
lastPointId = it.getLocationId();
counter++;
}
} finally {
it.close();
}
assertTrue(numPoints == 0 || lastPointId > 0);
assertEquals(numPoints, track.getNumberOfPoints());
assertEquals(numPoints, counter);
return lastPointId;
}
}
@@ -0,0 +1,55 @@
/*
* 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 com.google.android.apps.mytracks.content.WaypointCreationRequest.WaypointType;
import android.os.Parcel;
import android.test.AndroidTestCase;
/**
* Tests for the WaypointCreationRequest class.
* {@link WaypointCreationRequest}
*
* @author Sandor Dornbush
*/
public class WaypointCreationRequestTest extends AndroidTestCase {
public void testTypeParceling() {
WaypointCreationRequest original = WaypointCreationRequest.DEFAULT_MARKER;
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
assertEquals(original.getType(), copy.getType());
assertNull(copy.getName());
assertNull(copy.getDescription());
assertNull(copy.getIconUrl());
}
public void testAllAttributesParceling() {
WaypointCreationRequest original =
new WaypointCreationRequest(WaypointType.MARKER, "name", "description", "img.png");
Parcel p = Parcel.obtain();
original.writeToParcel(p, 0);
p.setDataPosition(0);
WaypointCreationRequest copy = WaypointCreationRequest.CREATOR.createFromParcel(p);
assertEquals(original.getType(), copy.getType());
assertEquals("name", copy.getName());
assertEquals("description", copy.getDescription());
assertEquals("img.png", copy.getIconUrl());
}
}
@@ -2,31 +2,27 @@
package com.google.android.apps.mytracks.io;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.leq;
import static org.easymock.EasyMock.same;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
import android.content.Context;
import android.database.MatrixCursor;
import android.location.Location;
import android.provider.BaseColumns;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.easymock.IArgumentMatcher;
import org.easymock.IMocksControl;
@@ -138,12 +134,19 @@ public class TrackWriterTest extends AndroidTestCase {
protected void setUp() throws Exception {
super.setUp();
MockContentResolver mockContentResolver = new MockContentResolver();
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
Context context = new MockContext(mockContentResolver, targetContext);
MyTracksProvider provider = new MyTracksProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
setContext(context);
providerUtils = MyTracksProviderUtils.Factory.get(context);
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils);
mocksControl = EasyMock.createStrictControl();
formatWriter = mocksControl.createMock(TrackFormatWriter.class);
providerUtils = mocksControl.createMock(MyTracksProviderUtils.class);
oldProviderUtilsFactory =
TestingProviderUtilsFactory.installWithInstance(providerUtils);
expect(formatWriter.getExtension()).andStubReturn(EXTENSION);
track = new Track();
@@ -225,13 +228,6 @@ public class TrackWriterTest extends AndroidTestCase {
public void testWriteDocument_emptyTrack() {
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
// Don't let it write any waypoints
expect(providerUtils.getWaypointsCursor(
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
.andStubReturn(null);
expect(providerUtils.getLocationsCursor(
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(null);
// Set expected mock behavior
formatWriter.writeHeader();
formatWriter.writeFooter();
@@ -253,7 +249,7 @@ public class TrackWriterTest extends AndroidTestCase {
new Location("fake2"),
new Location("fake3"),
new Location("fake4"),
new Location("fake5"),
new Location("fake5")
};
Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() };
@@ -262,37 +258,14 @@ public class TrackWriterTest extends AndroidTestCase {
// Make location 3 invalid
locs[2].setLatitude(100);
// Set up cursors
// We use fake columns since the cursor is only read by the provider utils
final MatrixCursor locCursor =
new MatrixCursor(new String[] { BaseColumns._ID }, 6);
for (int i = 1; i <= 6; i++) {
locCursor.newRow().add(i);
assertEquals(locs.length, providerUtils.bulkInsertTrackPoints(locs, locs.length, TRACK_ID));
for (int i = 0; i < wps.length; ++i) {
Waypoint wpt = wps[i];
wpt.setTrackId(TRACK_ID);
assertNotNull(providerUtils.insertWaypoint(wpt));
wpt.setId(i + 1);
}
expect(providerUtils.getLocationsCursor(
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(locCursor);
providerUtils.fillLocation(same(locCursor), isA(Location.class));
EasyMock.expectLastCall().andStubAnswer(new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
Location loc = (Location) EasyMock.getCurrentArguments()[1];
loc.set(locs[locCursor.getPosition()]);
return null;
}
});
MatrixCursor wpCursor =
new MatrixCursor(new String[] { BaseColumns._ID }, 3);
wpCursor.newRow().add(1);
wpCursor.newRow().add(2);
wpCursor.newRow().add(3);
expect(providerUtils.getWaypointsCursor(
eq(TRACK_ID), leq(0L),
eq(MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)))
.andStubReturn(wpCursor);
expect(providerUtils.createWaypoint(wpCursor))
.andStubAnswer(stubCursorToArray(wpCursor, wps));
// Begin the track
formatWriter.writeHeader();
@@ -317,8 +290,8 @@ public class TrackWriterTest extends AndroidTestCase {
formatWriter.writeEndTrack(locEq(locs[5]));
// Expect reading/writing of the waypoints (except the first)
formatWriter.writeWaypoint(wps[1]);
formatWriter.writeWaypoint(wps[2]);
formatWriter.writeWaypoint(wptEq(wps[1]));
formatWriter.writeWaypoint(wptEq(wps[2]));
formatWriter.writeFooter();
formatWriter.close();
@@ -330,6 +303,26 @@ public class TrackWriterTest extends AndroidTestCase {
mocksControl.verify();
}
private static Waypoint wptEq(final Waypoint wpt) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object wptObj2) {
if (wptObj2 == null || wpt == null) return wpt == wptObj2;
Waypoint wpt2 = (Waypoint) wptObj2;
return wpt.getId() == wpt2.getId();
}
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("wptEq(");
buffer.append(wpt);
buffer.append(")");
}
});
return null;
}
private static Location locEq(final Location loc) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
@@ -359,23 +352,14 @@ public class TrackWriterTest extends AndroidTestCase {
});
return null;
}
private <T> IAnswer<T> stubCursorToArray(
final MatrixCursor cursor, final T[] values) {
return new IAnswer<T>() {
@Override
public T answer() throws Throwable {
return values[cursor.getPosition()];
}
};
}
private void fillLocations(Location... locs) {
assertTrue(locs.length < 90);
for (int i = 0; i < locs.length; i++) {
Location location = locs[i];
location.setLatitude(i + 1);
location.setLongitude(i + 1);
location.setTime(i + 1000);
}
}
}
@@ -38,7 +38,7 @@ public class PreferenceBackupHelperTest extends TestCase {
*/
private class MockPreferenceEditor implements SharedPreferences.Editor {
private Map<String, Object> newPreferences = new HashMap<String, Object>(preferenceValues);
@Override
public Editor clear() {
newPreferences.clear();
@@ -51,6 +51,11 @@ public class PreferenceBackupHelperTest extends TestCase {
return true;
}
@Override
public void apply() {
commit();
}
@Override
public Editor putBoolean(String key, boolean value) {
return put(key, value);
@@ -0,0 +1,101 @@
/*
* 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.io.docs;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import android.content.Context;
import android.test.mock.MockContext;
import junit.framework.TestCase;
import java.io.IOException;
/**
* Tests for {@link DocsHelper}, with the exception of
* {@link DocsHelper#addTrackRow}, which is in
* {@link DocsHelper_AddTrackRowTest}.
*
* @author Matthew Simmons
*/
public class DocsHelperTest extends TestCase {
private Context mockContext = new MockContext();
// TODO(simmonmt): Use AndroidMock to mock this class. We do it the hard
// way because use of AndroidMock to mock GDataWrapper triggers a compile
// error in an unrelated file. Specifically, it causes a NoClassDefFound
// exception for com.google.wireless.gdata2.client.AuthenticationException,
// (wrongly) attributed to the first source file in the project. Using the
// @UsesMocks(GDataWrapper.class) annotation is enough -- you don't have to
// touch AndroidMock at all to get this failure.
// The bug is filed with Android Mock as
// http://code.google.com/p/android-mock/issues/detail?id=3
private class MockGDataWrapper extends GDataWrapper {
private final boolean returnValue;
MockGDataWrapper(boolean returnValue) {
this.returnValue = returnValue;
}
@Override
public boolean runQuery(QueryFunction queryFunction) {
return returnValue;
}
}
public void testCreateSpreadsheet_noException() throws Exception {
// Our mock GDataWrapper isn't able to affect the return value from
// DocsHelper#createSpreadsheet. As such, we're only able to simulate the
// case where there weren't any GData errors, but not spreadsheet ID was
// actually returned. createSpreadsheet is defined to return null in that
// situation.
assertNull(new DocsHelper().createSpreadsheet(
mockContext, new MockGDataWrapper(true), "sheetName"));
}
public void testCreateSpreadsheet_exception() throws Exception {
try {
new DocsHelper().createSpreadsheet(
mockContext, new MockGDataWrapper(false), "sheetName");
fail();
} catch (IOException expected) {}
}
public void testRequestSpreadsheetId_noException() throws Exception {
assertNull(new DocsHelper().requestSpreadsheetId(
new MockGDataWrapper(true), "sheetName"));
}
public void testRequestSpreadsheetId_exception() throws Exception {
try {
new DocsHelper().requestSpreadsheetId(new MockGDataWrapper(false),
"sheetName");
fail();
} catch (IOException expected) {}
}
public void testGetWorksheetId_noException() throws Exception {
assertNull(new DocsHelper().getWorksheetId(new MockGDataWrapper(true),
"sheetId"));
}
public void testGetWorksheetId_exception() throws Exception {
try {
new DocsHelper().getWorksheetId(new MockGDataWrapper(false), "sheetId");
fail();
} catch (IOException expected) {}
}
}
@@ -0,0 +1,159 @@
/*
* 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.io.docs;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.testing.mocking.AndroidMock;
import com.google.android.testing.mocking.UsesMocks;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.res.Resources;
import android.test.mock.MockContext;
import android.test.mock.MockResources;
import junit.framework.TestCase;
import java.io.IOException;
/**
* Tests for {@link DocsHelper#addTrackRow}
*
* @author Matthew Simmons
*/
public class DocsHelper_AddTrackRowTest extends TestCase {
private static final long TIME = 1288721514000L;
private static class StringWritingDocsHelper extends DocsHelper {
String writtenSheetUri = null;
String writtenData = null;
@Override
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
String postText) {
writtenSheetUri = worksheetUri;
writtenData = postText;
}
}
public void testAddTrackRow_imperial() throws Exception {
StringWritingDocsHelper docsHelper = new StringWritingDocsHelper();
addTrackRow(docsHelper, false);
String expectedData =
"<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>"
+ "<gsx:name><![CDATA[trackName]]></gsx:name>"
+ "<gsx:description><![CDATA[trackDescription]]></gsx:description>"
// We format the date here because we can't guarantee/force the timezone
+ "<gsx:date><![CDATA[" + String.format("%tc", TIME) + "]]></gsx:date>"
+ "<gsx:totaltime><![CDATA[0:00:05]]></gsx:totaltime>"
+ "<gsx:movingtime><![CDATA[0:00:04]]></gsx:movingtime>"
+ "<gsx:distance><![CDATA[12.43]]></gsx:distance>"
+ "<gsx:distanceunit><![CDATA[mile]]></gsx:distanceunit>"
+ "<gsx:averagespeed><![CDATA[8,947.75]]></gsx:averagespeed>"
+ "<gsx:averagemovingspeed><![CDATA[11,184.68]]>"
+ "</gsx:averagemovingspeed>"
+ "<gsx:maxspeed><![CDATA[3,355.40]]></gsx:maxspeed>"
+ "<gsx:speedunit><![CDATA[mph]]></gsx:speedunit>"
+ "<gsx:elevationgain><![CDATA[19,685]]></gsx:elevationgain>"
+ "<gsx:minelevation><![CDATA[-1,640]]></gsx:minelevation>"
+ "<gsx:maxelevation><![CDATA[1,804]]></gsx:maxelevation>"
+ "<gsx:elevationunit><![CDATA[feet]]></gsx:elevationunit>"
+ "<gsx:map>"
+ "<![CDATA[http://maps.google.com/maps/ms?msa=0&msid=trackMapId]]>"
+ "</gsx:map>"
+ "</entry>";
assertEquals(
"http://spreadsheets.google.com/feeds/list/ssid/wsid/private/full",
docsHelper.writtenSheetUri);
assertEquals(expectedData, docsHelper.writtenData);
}
public void testAddTrackRow_metric() throws Exception {
StringWritingDocsHelper docsHelper = new StringWritingDocsHelper();
addTrackRow(docsHelper, true);
// The imperial test verifies that the tags come out in the proper order,
// and with the proper names. We need only verify that the labels are
// correct, and that at least one of the unit-dependent value tags is
// correct.
assertTrue(docsHelper.writtenData.contains(
"<gsx:distanceunit><![CDATA[km]]></gsx:distanceunit>"));
assertTrue(docsHelper.writtenData.contains(
"<gsx:speedunit><![CDATA[kph]]></gsx:speedunit>"));
assertTrue(docsHelper.writtenData.contains(
"<gsx:elevationunit><![CDATA[meter]]></gsx:elevationunit>"));
assertTrue(docsHelper.writtenData.contains(
"<gsx:distance><![CDATA[20.00]]></gsx:distance>"));
}
@UsesMocks({AuthManager.class, MockContext.class, MockResources.class,
Track.class})
/** Adds a row to the spreadsheet, using the provided helper. */
private void addTrackRow(DocsHelper docsHelper, boolean useMetric)
throws IOException {
Resources mockResources = AndroidMock.createMock(MockResources.class);
if (useMetric) {
AndroidMock.expect(mockResources.getString(R.string.kilometer))
.andReturn("km");
AndroidMock.expect(mockResources.getString(R.string.kilometer_per_hour))
.andReturn("kph");
AndroidMock.expect(mockResources.getString(R.string.meter))
.andReturn("meter");
} else {
AndroidMock.expect(mockResources.getString(R.string.mile))
.andReturn("mile");
AndroidMock.expect(mockResources.getString(R.string.mile_per_hour))
.andReturn("mph");
AndroidMock.expect(mockResources.getString(R.string.feet))
.andReturn("feet");
}
AndroidMock.replay(mockResources);
Context mockContext = AndroidMock.createMock(MockContext.class);
AndroidMock.expect(mockContext.getResources())
.andReturn(mockResources).anyTimes();
AndroidMock.replay(mockContext);
AuthManager mockAuthManager = AndroidMock.createMock(AuthManager.class);
AndroidMock.replay(mockAuthManager);
TripStatistics stats = new TripStatistics();
stats.setStartTime(TIME);
stats.setTotalTime(5000);
stats.setMovingTime(4000);
stats.setTotalDistance(20000);
stats.setMaxSpeed(1500);
stats.setTotalElevationGain(6000);
stats.setMinElevation(-500);
stats.setMaxElevation(550);
Track track = new Track();
track.setName("trackName");
track.setDescription("trackDescription");
track.setMapId("trackMapId");
track.setStatistics(stats);
docsHelper.addTrackRow(mockContext, mockAuthManager, "ssid", "wsid",
track, useMetric);
}
}
@@ -0,0 +1,60 @@
/*
* 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.io.docs;
import junit.framework.TestCase;
/**
* This class tests {@link DocsTagBuilder}
*
* @author Matthew Simmons
*/
public class DocsTagBuilderTest extends TestCase {
public void testAppend() {
assertEquals("<gsx:tag><![CDATA[value]]></gsx:tag>",
new DocsTagBuilder(false).append("tag", "value").build());
}
public void testMultiAppend() {
String actual = new DocsTagBuilder(false)
.append("tag1", "value1")
.append("tag2", "value2")
.build();
assertEquals("<gsx:tag1><![CDATA[value1]]></gsx:tag1>"
+ "<gsx:tag2><![CDATA[value2]]></gsx:tag2>", actual);
}
public void testAppendLargeUnits_imperial() {
assertEquals("<gsx:tag><![CDATA[62.14]]></gsx:tag>",
new DocsTagBuilder(false).appendLargeUnits("tag", 100.0).build());
}
public void testAppendLargeUnits_metric() {
assertEquals("<gsx:tag><![CDATA[100.00]]></gsx:tag>",
new DocsTagBuilder(true).appendLargeUnits("tag", 100.0).build());
}
public void testAppendSmallUnits_imperial() {
assertEquals("<gsx:tag><![CDATA[328]]></gsx:tag>",
new DocsTagBuilder(false).appendSmallUnits("tag", 100.0).build());
}
public void testAppendSmallUnits_metric() {
assertEquals("<gsx:tag><![CDATA[100]]></gsx:tag>",
new DocsTagBuilder(true).appendSmallUnits("tag", 100.0).build());
}
}
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
@@ -77,9 +78,11 @@ public class TrackRecordingServiceTest
}
/**
* A context wrapper with the user provided {@link ContentResolver}.
* A context wrapper with the user provided {@link ContentResolver}.
*
* TODO: Move to test utils package.
*/
private static class MockContext extends ContextWrapper {
public static class MockContext extends ContextWrapper {
private final ContentResolver contentResolver;
public MockContext(ContentResolver contentResolver, Context base) {
@@ -499,7 +502,7 @@ public class TrackRecordingServiceTest
public void testDeleteAllTracks_trackInProgress() throws Exception {
createDummyTrack(123, -1, true);
assertEquals(1, providerUtils.getAllTracks().size());
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
@@ -546,9 +549,8 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
Location loc = new Location("gps");
try {
service.insertStatisticsMarker(loc);
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
fail("Expecting IllegalStateException");
} catch (IllegalStateException e) {
// Expected.
@@ -562,11 +564,22 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
Location loc = new Location("gps");
assertEquals(1, service.insertStatisticsMarker(loc));
assertEquals(2, service.insertStatisticsMarker(loc));
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
// TODO: Add more checks.
Waypoint wpt = providerUtils.getWaypoint(1);
assertEquals(getContext().getString(R.string.stats_icon_url),
wpt.getIcon());
assertEquals(getContext().getString(R.string.statistics),
wpt.getName());
assertEquals(Waypoint.TYPE_STATISTICS, wpt.getType());
assertEquals(123, wpt.getTrackId());
assertEquals(0.0, wpt.getLength());
assertNotNull(wpt.getLocation());
assertNotNull(wpt.getStatistics());
// TODO check the rest of the params.
// TODO: Check waypoint 2.
}
@MediumTest
@@ -574,29 +587,14 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertFalse(service.isRecording());
Location loc = new Location("gps");
Waypoint waypoint = new Waypoint();
waypoint.setId(1);
waypoint.setLocation(loc);
try {
service.insertWaypointMarker(waypoint);
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
fail("Expecting IllegalStateException");
} catch (IllegalStateException e) {
// Expected.
}
}
@MediumTest
public void testInsertWaypointMarker_invalidWaypoint() throws Exception {
createDummyTrack(123, -1, true);
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
Waypoint waypoint = new Waypoint();
assertEquals(-1, service.insertWaypointMarker(waypoint));
}
@MediumTest
public void testInsertWaypointMarker_validWaypoint() throws Exception {
createDummyTrack(123, -1, true);
@@ -604,11 +602,17 @@ public class TrackRecordingServiceTest
ITrackRecordingService service = bindAndGetService(createStartIntent());
assertTrue(service.isRecording());
Location loc = new Location("gps");
Waypoint waypoint = new Waypoint();
waypoint.setId(1);
waypoint.setLocation(loc);
assertEquals(1, service.insertWaypointMarker(waypoint));
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER));
Waypoint wpt = providerUtils.getWaypoint(1);
assertEquals(getContext().getString(R.string.waypoint_icon_url),
wpt.getIcon());
assertEquals(getContext().getString(R.string.waypoint),
wpt.getName());
assertEquals(Waypoint.TYPE_WAYPOINT, wpt.getType());
assertEquals(123, wpt.getTrackId());
assertEquals(0.0, wpt.getLength());
assertNotNull(wpt.getLocation());
assertNull(wpt.getStatistics());
}
@MediumTest
@@ -804,11 +808,9 @@ public class TrackRecordingServiceTest
service.recordLocation(loc);
if (i % 10 == 0) {
service.insertStatisticsMarker(loc);
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
} else if (i % 7 == 0) {
Waypoint waypoint = new Waypoint();
waypoint.setLocation(loc);
service.insertWaypointMarker(waypoint);
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
}
}
@@ -0,0 +1,97 @@
/*
* 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.services.sensors;
import com.dsi.ant.AntMesg;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
import android.content.SharedPreferences;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
public class AntDirectSensorManagerTest extends AndroidTestCase {
private SharedPreferences sharedPreferences;
private AntDirectSensorManager manager;
public void setUp() {
sharedPreferences = getContext().getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
// Let's use default values.
sharedPreferences.edit().clear().commit();
manager = new AntDirectSensorManager(getContext());
}
@SmallTest
public void testBroadcastData() {
manager.setDeviceNumberHRM((short) 42);
byte[] buff = new byte[11];
buff[1] = AntMesg.MESG_BROADCAST_DATA_ID;
buff[2] = 0; // HRM CHANNEL
buff[10] = (byte) 220;
manager.handleMessage(buff);
Sensor.SensorDataSet sds = manager.getSensorDataSet();
assertNotNull(sds);
assertTrue(sds.hasHeartRate());
assertEquals(Sensor.SensorState.SENDING,
sds.getHeartRate().getState());
assertEquals(220, sds.getHeartRate().getValue());
assertFalse(sds.hasCadence());
assertFalse(sds.hasPower());
}
@SmallTest
public void testHandleChannelId() {
assertEquals(0, manager.getDeviceNumberHRM());
short id = 42;
byte[] buff = new byte[5];
buff[3] = 42;
manager.handleChannelId(buff);
assertEquals(id, manager.getDeviceNumberHRM());
}
@SmallTest
public void testChannelId() {
byte[] buff = new byte[11];
buff[1] = AntMesg.MESG_CHANNEL_ID_ID;
buff[3] = 42;
manager.handleMessage(buff);
assertEquals(42, manager.getDeviceNumberHRM());
assertEquals(42,
sharedPreferences.getInt(
getContext().getString(R.string.ant_heart_rate_sensor_id_key), -1));
assertNull(manager.getSensorDataSet());
}
@SmallTest
public void testResponseEvent() {
assertEquals(Sensor.SensorState.NONE, manager.getSensorState());
byte[] buff = new byte[11];
buff[1] = AntMesg.MESG_RESPONSE_EVENT_ID;
buff[3] = AntMesg.MESG_UNASSIGN_CHANNEL_ID;
manager.handleMessage(buff);
assertEquals(Sensor.SensorState.DISCONNECTED, manager.getSensorState());
assertNull(manager.getSensorDataSet());
}
// TODO: Test timeout too.
}
@@ -28,12 +28,26 @@ public class SensorManagerFactoryTest extends AndroidTestCase {
@SmallTest
public void testCreateZephyr() throws Exception {
assertClassForName(ZephyrSensorManager.class, R.string.zephyr_sensor_type);
}
@SmallTest
public void testCreateAnt() throws Exception {
assertClassForName(AntDirectSensorManager.class, R.string.ant_sensor_type);
}
@SmallTest
public void testCreateAntSRM() throws Exception {
assertClassForName(AntSRMSensorManager.class, R.string.srm_ant_bridge_sensor_type);
}
private void assertClassForName(Class<?> c, int i) {
sharedPreferences.edit()
.putString(getContext().getString(R.string.sensor_type_key),
getContext().getString(R.string.zephyr_sensor_type))
.commit();
.putString(getContext().getString(R.string.sensor_type_key),
getContext().getString(i))
.commit();
SensorManager sm = SensorManagerFactory.getSensorManager(getContext());
assertNotNull(sm);
assertTrue(sm instanceof ZephyrSensorManager);
assertTrue(c.isInstance(sm));
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="MyTracksLib_src"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
<classpathentry kind="src" path="MyTracksLib_src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+2 -1
View File
@@ -54,7 +54,8 @@ limitations under the License.
<uses-feature android:name="android.hardware.telephony" android:required="true"/>
<uses-feature android:name="android.hardware.telephony.gsm" android:required="false"/>
<uses-sdk android:minSdkVersion="3" />
<supports-screens android:largeScreens="true"
<supports-screens android:xlargeScreens="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true" />
+2 -1
View File
@@ -8,5 +8,6 @@
# project structure.
# Project target.
target=Google Inc.:Google APIs:8
target=Google Inc.:Google APIs:9
proguard.config=proguard.cfg
android.library.reference.1=../MyTracksLib/
@@ -17,7 +17,9 @@ package com.google.android.apps.mytracks.signalstrength;
import static com.google.android.apps.mytracks.signalstrength.SignalStrengthConstants.*;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.signalstrength.SignalStrengthListener.SignalStrengthCallback;
@@ -154,13 +156,6 @@ public class SignalStrengthService extends Service
return;
}
// We don't set location or track id - My Tracks can fill those
Waypoint wpt = new Waypoint();
wpt.setName("Signal Strength");
wpt.setType(Waypoint.TYPE_WAYPOINT);
wpt.setIcon(icon);
wpt.setDescription(description);
try {
long waypointId;
synchronized (this) {
@@ -169,7 +164,11 @@ public class SignalStrengthService extends Service
return;
}
waypointId = mytracksService.insertWaypointMarker(wpt);
// Create a waypoint.
WaypointCreationRequest request =
new WaypointCreationRequest(WaypointCreationRequest.WaypointType.MARKER,
"Signal Strength", icon, description);
waypointId = mytracksService.insertWaypoint(request);
}
if (waypointId >= 0) {
+2 -2
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry combineaccessrules="false" exported="true" kind="src" path="/SignalStrengthSampler"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry combineaccessrules="false" kind="src" path="/SignalStrengthSampler"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="libs/AndroidMockRuntime.jar"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+1 -1
View File
@@ -8,4 +8,4 @@
# project structure.
# Project target.
target=Google Inc.:Google APIs:8
target=Google Inc.:Google APIs:9