Merge from head.

This commit is contained in:
Sandor Dornbush
2011-01-13 21:07:15 -08:00
79 changed files with 2219 additions and 3126 deletions
+1
View File
@@ -7,6 +7,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
<classpathentry kind="lib" path="libs/google-api-client-1.2.1-alpha.jar"/>
<classpathentry kind="src" path="MyTracksLib_src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
-5
View File
@@ -56,11 +56,6 @@ limitations under the License.
android:label="MyTracks"
android:exported="false" />
<activity android:name="com.google.android.apps.mymaps.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.MyTracksList"
android:label="Recorded Tracks" />
+2 -2
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml version="1.0" encoding="UTF-8"?>
<launchConfiguration type="com.android.ide.eclipse.adt.debug.LaunchConfigType">
<intAttribute key="com.android.ide.eclipse.adt.action" value="0"/>
<stringAttribute key="com.android.ide.eclipse.adt.commandline" value=""/>
@@ -16,8 +16,8 @@
<listEntry value="1"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ALLOW_TERMINATE" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="MyTracks"/>
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

-103
View File
@@ -1,103 +0,0 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apl/res/com.google.android.maps"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:layout_weight="0"
android:padding="3dip">
<TextView android:id="@+id/message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="16dip"
android:textColor="#fff"
android:text="@string/select_map_message"
android:layout_weight="0"/>
</LinearLayout>
<ListView android:id="@+id/maplist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView android:id="@+id/mapslist_empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:gravity="center"
android:padding="3dip"
android:textSize="16dip"
android:textColor="#c00"
android:textStyle="bold"
android:text="@string/mapslist_no_maps_available"
android:visibility="gone"/>
<TextView android:id="@+id/failed"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="3dip"
android:textSize="16dip"
android:textColor="#c00"
android:textStyle="bold"
android:text="@string/maps_list_failed"
android:visibility="gone"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:layout_weight="0"
android:padding="3dip">
<LinearLayout android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dip"
android:layout_gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<ProgressBar
android:layout_height="20dip"
android:layout_width="20dip"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="3dip"
android:textSize="16dip"
android:textColor="#fff"
android:textStyle="bold"
android:text="@string/maps_list_loading"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
@@ -42,7 +42,7 @@ limitations under the License.
android:layout_marginTop="5dip"
android:layout_marginLeft="5dip">
<TableRow>
<ImageView android:src="@drawable/mymaps_icon"
<ImageView android:src="@drawable/googledocs_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
@@ -50,24 +50,12 @@ limitations under the License.
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dip">
<CheckBox android:id="@+id/sendtogoogle_google_mymaps"
<CheckBox android:id="@+id/sendtogoogle_google_fusiontables"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_google_mymaps"
android:text="@string/sendtogoogle_google_fusiontables"
android:textSize="20dip"
android:checked="true"/>
<RadioGroup android:id="@+id/sendtogoogle_group_mymaps"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RadioButton android:id="@+id/sendtogoogle_create_new_map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_create_new_map"/>
<RadioButton android:id="@+id/sendtogoogle_pick_existing_map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_pick_existing_map"/>
</RadioGroup>
</LinearLayout>
</TableRow>
<TableRow>
+5 -5
View File
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_start_recording"
android:icon="@drawable/menu_record"
android:icon="@drawable/ic_menu_recordtrack"
android:title="@string/start_recording"/>
<item android:id="@+id/menu_stop_recording"
android:icon="@drawable/menu_stop"
android:icon="@drawable/ic_menu_stoprecording"
android:title="@string/stop_recording"/>
<item android:id="@+id/menu_list_tracks"
android:icon="@drawable/menu_list_tracks"
android:icon="@drawable/ic_menu_tracklist"
android:title="@string/list_tracks"/>
<item android:id="@+id/menu_list_markers"
android:icon="@drawable/menu_marker"
android:icon="@drawable/ic_menu_waypoints"
android:title="@string/list_markers"/>
<item android:id="@+id/menu_settings"
@@ -26,7 +26,7 @@
android:title="@string/aggregated_stats"/>
<item android:id="@+id/menu_sensor_state"
android:icon="@drawable/menu_sensor_status"
android:icon="@drawable/ic_menu_sensorstatus"
android:orderInCategory="1000"
android:title="@string/sensor_state"/>
+5 -23
View File
@@ -54,7 +54,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">S\'ha enviat la ruta a maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">S\'ha enviat la ruta a un full de càlcul a docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">S\'ha enviat la ruta a docs.google.com.</string>
<string name="status_mymap_info">Poden passar uns minuts abans que les rutes noves apareguin.</string>
<string name="status_enable_gps">Piqueu aquí per anar als paràmetres de seguretat i ubicació i activeu \'habilita els satèl·lits GPS\'. Premeu el botó d\'anar enrere per tornar.</string>
<string name="status_approximate_location">Nota: Es mostra una ubicació determinada a partir de les xarxes sense fils i antenes telefòniques.</string>
<string name="status_no_location">No es pot determinar la ubicació. Habiliteu més fonts d\'ubicació (vegeu els paràmetres de seguretat i ubicació) i assegureu-vos que el cel és visible. Proveu de nou més tard.</string>
@@ -104,6 +103,10 @@ limitations under the License.
<string name="error">Error</string>
<string name="success">Fet</string>
<string name="waypoint">Punt de referència</string>
<string name="cancel">Cancel·la</string>
<string name="close">Tanca</string>
<string name="edit">Edita</string>
<string name="save">Desa</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Inici)</string>
@@ -241,28 +244,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satèl·lits GPS</string>
<!-- My Maps -->
<string name="select_map_message">Seleccioneu un mapa del vostre compte:</string>
<string name="create_new_map">Crea un mapa nou...</string>
<string name="map_list_title">Envia a Google My Maps</string>
<string name="maps_list_loading">S\'estan carregant els mapes...</string>
<string name="maps_list_failed">No s\'han pogut carregar els vostres mapes en aquest moment</string>
<string name="open_map">Selecciona aquest mapa</string>
<string name="edit_map_details">Edita els detalls del mapa</string>
<string name="share_map">Comparteix el mapa amb amics</string>
<string name="share_track">Comparteix la ruta amb amics</string>
<string name="delete_map">Elimina el mapa del vostre compte</string>
<string name="map_title">Títol del mapa:</string>
<string name="map_description">Descripció del mapa:</string>
<string name="map_searchable_edit">Fes que aquest mapa i els seus continguts apareguen als resultats de cerca</string>
<string name="map_not_searchable_edit">No permetis que aquest mapa ni els seus continguts es mostrin als resultats de cerca, tot i que sí es podrà visualitzar per altres amb l\'adreça URL directa</string>
<string name="map_searchable">Aquest mapa i els seus continguts es mostraran als resultats de cerca</string>
<string name="map_not_searchable">Ni els mapes ni els seus continguts es mostraran als resultats de cerca, però podran visualitzar-se per altres a través d\'un enllaç directe.</string>
<string name="close">Tanca</string>
<string name="edit">Edita</string>
<string name="cancel">Cancel·la</string>
<string name="save">Desa</string>
<!-- Sharing -->
<string name="share_map_subject">M\'agradaria compartir un mapa amb tu</string>
<string name="share_map_body_format">Crec que estaràs interessat en aquest mapa:
@@ -272,6 +253,7 @@ limitations under the License.
<string name="send_track_body_format">Crec que estaràs interessat en aquesta ruta.
Per verue aquesta ruta fes servir Google Earth o altre programari de mapes.</string>
<string name="share_track">Comparteix la ruta amb amics</string>
<!-- Visibility -->
<string name="public_map">Públic</string>
+390 -410
View File
@@ -1,398 +1,378 @@
<?xml version="1.0" encoding="utf-8"?>
<?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>
<!-- Used by the Menu -->
<string name="start_recording">Track aufzeichnen</string>
<string name="stop_recording">Aufzeichnung anhalten</string>
<string name="switch_to_sat">Satellitenansicht</string>
<string name="switch_to_map">Kartenansicht</string>
<string name="mylocation">Mein Aufenthaltsort</string>
<string name="list_markers">Wegpunkte</string>
<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ä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 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_mymap">Beim Senden des Tracks an Google MyMaps 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 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ß (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">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 angehalten.</string>
<string name="status_new_mymap_has_been_created">Eine neue Karte wurde auf maps.google.com 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">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_mymaps">Track wird an Google Maps gesendet...</string>
<string name="progress_message_creating_map">Neue \'Meine Karte\' 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>
<string name="all_data_will_be_permanently_deleted">Alle Tracks und Wegpunkte werden endgültig von diesem Gerät gelöscht.</string>
<string name="track_will_be_permanently_deleted">Dieser Track und seine Wegpunkte werden endgültig von diesem Gerät gelöscht.</string>
<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 MyMaps -->
<string name="new_map_description">Erstellt von My Tracks für Android.</string>
<string name="my_map_link">Erstellt von &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; für Android.</string>
<string name="new_map_title">Neue Karte</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>
<!-- Used by the track recording service -->
<string name="recording_your_track">Dein Track wird aufgezeichnet…</string>
<!-- Common -->
<string name="no">Nein</string>
<string name="yes">Ja</string>
<string name="error">Fehler</string>
<string name="success">Erfolgreich</string>
<string name="waypoint">Wegpunkt</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Beginn)</string>
<string name="end">(Ende)</string>
<!-- TrackList -->
<string name="tracklist_show_track">Anzeigen</string>
<string name="tracklist_send_to_mymaps">An eine Google MyMaps-Karte senden</string>
<string name="tracklist_send_to_new_mymap">An eine neue Google MyMaps-Karte senden</string>
<string name="tracklist_send_to_docs">An Google Docs senden</string>
<string name="tracklist_send_to_google">An Google senden…</string>
<string name="tracklist_delete_track">Vom Gerät löschen…</string>
<string name="tracklist_edit_track">Bearbeiten</string>
<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>
<!-- 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_link">Ein Link zur Google MyMaps-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 -->
<string name="waypointslist_btn_delete_all">Alle Wegpunkte löschen</string>
<string name="waypointslist_this_waypoint">Dieser Wegpunkt</string>
<string name="waypointslist_delete_waypoint">Löschen</string>
<string name="waypointslist_edit_waypoint">Details bearbeiten</string>
<string name="waypointslist_show_waypoint">Auf der Karte zeigen</string>
<string name="waypointslist_btn_insert_waypoint">Wegpunkt einfügen</string>
<string name="waypointslist_btn_insert_statistics">Statistik einfügen</string>
<string name="waypointslist_pick_one">Wegpunkt auswählen. Lange berühren für mehr Optionen.</string>
<!-- MyTracks Details -->
<string name="trackdetails_title">Trackdetails</string>
<string name="trackdetails_name">Name</string>
<string name="trackdetails_description">Beschreibung</string>
<string name="trackdetails_category">Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…)</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Wegpunktdetails</string>
<string name="waypointdetails_name">Wegpunktname</string>
<string name="waypointdetails_description">Wegpunktbeschreibung</string>
<string name="waypointdetails_category">Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…)</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Einstellungen für My Tracks</string>
<string name="settings_display">Anzeige</string>
<string name="settings_recording">Aufzeichnung</string>
<string name="settings_sharing">Mit Freunden teilen</string>
<string name="settings_not_available_summary">In dieser Android-Version nicht verfügbar</string>
-->
<resources>
<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_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_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 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 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_summary_on">Geschwindigkeit 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_split_frequency">Zerteilungsfrequenz</string>
<string name="settings_split_frequency_summary">Längste Dauer oder Entfernung, nach der automatisch ein Marker eingefügt wird.</string>
<!-- Used by the Menu -->
<string name="start_recording">Track aufzeichnen</string>
<string name="stop_recording">Aufzeichnung anhalten</string>
<string name="switch_to_sat">Satellitenansicht</string>
<string name="switch_to_map">Kartenansicht</string>
<string name="mylocation">Mein Aufenthaltsort</string>
<string name="list_markers">Wegpunkte</string>
<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ä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>
<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>
<!-- MyMaps -->
<string name="select_map_message">Wähle eine Karte von Deinem Konto aus</string>
<string name="create_new_map">Neue Karte erstellen…</string>
<string name="map_list_title">An Google My Maps senden</string>
<string name="maps_list_loading">Karten werden geladen…</string>
<string name="maps_list_failed">Deine Karten konnten im Augenblick nicht geladen werden!</string>
<string name="open_map">Diese Karte auswählen</string>
<string name="edit_map_details">Kartendetails bearbeiten</string>
<string name="share_map">Karte mit Freunden teilen</string>
<string name="share_track">Track mit Freunden teilen</string>
<string name="delete_map">Karte vom Konto löschen</string>
<string name="map_title">Kartentitel</string>
<string name="map_description">Trackbeschreibung</string>
<string name="map_searchable_edit">Diese Karte und ihren Inhalt in Suchergebnissen sichtbar machen.</string>
<string name="map_not_searchable_edit">Diese Karte und ihren Inhalt nicht in Suchergebnissen sichtbar machen.</string>
<string name="map_searchable">Diese Karte und ihr Inhalt sind in Suchergebnissen sichtbar.</string>
<string name="map_not_searchable">Weder diese Karte noch ihr Inhalt sind in Suchergebnissen sichtbar, allerdings kann sie trotzdem von anderen durch einen direkten Link angezeigt werden.</string>
<string name="close">Schließen</string>
<string name="edit">Bearbeiten</string>
<!-- Error messages -->
<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 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ß (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">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 angehalten.</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">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_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">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_fusiontables">Anmeldung bei Google Fusion Tables...</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>
<string name="all_data_will_be_permanently_deleted">Alle Tracks und Wegpunkte werden endgültig von diesem Gerät gelöscht.</string>
<string name="track_will_be_permanently_deleted">Dieser Track und seine Wegpunkte werden endgültig von diesem Gerät gelöscht.</string>
<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="my_map_link">Erstellt von &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; für Android.</string>
<string name="new_map_title">Neue Karte</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>
<!-- Used by the track recording service -->
<string name="recording_your_track">Dein Track wird aufgezeichnet…</string>
<!-- Common -->
<string name="no">Nein</string>
<string name="yes">Ja</string>
<string name="error">Fehler</string>
<string name="success">Erfolgreich</string>
<string name="waypoint">Wegpunkt</string>
<string name="cancel">Abbrechen</string>
<string name="edit">Bearbeiten</string>
<string name="save">Sichern</string>
<!-- Sharing -->
<string name="share_map_subject">Ich möchte gerne eine Karte mit Dir teilen.</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Beginn)</string>
<string name="end">(Ende)</string>
<!-- TrackList -->
<string name="tracklist_show_track">Anzeigen</string>
<string name="tracklist_send_to_mymaps">An eine Google MyMaps-Karte senden</string>
<string name="tracklist_send_to_new_mymap">An eine neue Google MyMaps-Karte senden</string>
<string name="tracklist_send_to_docs">An Google Docs senden</string>
<string name="tracklist_send_to_google">An Google senden…</string>
<string name="tracklist_delete_track">Vom Gerät löschen…</string>
<string name="tracklist_edit_track">Bearbeiten</string>
<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>
<!-- 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 -->
<string name="waypointslist_btn_delete_all">Alle Wegpunkte löschen</string>
<string name="waypointslist_this_waypoint">Dieser Wegpunkt</string>
<string name="waypointslist_delete_waypoint">Löschen</string>
<string name="waypointslist_edit_waypoint">Details bearbeiten</string>
<string name="waypointslist_show_waypoint">Auf der Karte zeigen</string>
<string name="waypointslist_btn_insert_waypoint">Wegpunkt einfügen</string>
<string name="waypointslist_btn_insert_statistics">Statistik einfügen</string>
<string name="waypointslist_pick_one">Wegpunkt auswählen. Lange berühren für mehr Optionen.</string>
<!-- MyTracks Details -->
<string name="trackdetails_title">Trackdetails</string>
<string name="trackdetails_name">Name</string>
<string name="trackdetails_description">Beschreibung</string>
<string name="trackdetails_category">Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…)</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Wegpunktdetails</string>
<string name="waypointdetails_name">Wegpunktname</string>
<string name="waypointdetails_description">Wegpunktbeschreibung</string>
<string name="waypointdetails_category">Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…)</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Einstellungen für My Tracks</string>
<string name="settings_display">Anzeige</string>
<string name="settings_recording">Aufzeichnung</string>
<string name="settings_sharing">Mit Freunden teilen</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_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_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 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 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_summary_on">Geschwindigkeit 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_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>
<!-- Sharing -->
<string name="share_map">Karte mit Freunden teilen</string>
<string name="share_map_subject">Ich möchte gerne eine Karte mit Dir teilen.</string>
<string name="share_map_body_format">Vielleicht interessierst Du Dich für diese Karte:
%s</string>
<string name="send_track_subject">Ich möchte einen GPS-Track mit Dir teilen</string>
%s</string>
<string name="send_track_subject">Ich möchte einen GPS-Track mit Dir teilen</string>
<string name="send_track_body_format">Vielleicht interessierst Du Dich für diese Track:
%s.</string>
<string name="share_track">Track mit Freunden teilen</string>
<!-- Visibility -->
<string name="public_map">Öffentlich</string>
<string name="unlisted_map">Nicht in Suchergebnissen enthalten.</string>
<string name="mapslist_no_maps_available">Du hast noch keine Karten.</string>
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_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">Durchschnitts-Pace</string>
<string name="elevation_chart_name">Höhenprofil</string>
<string name="elevation_gain_label">Höhengewinn</string>
<string name="elevation_label">Höhe</string>
<string name="latitude_label">Breitengrad</string>
<string name="longitude_label">Längengrad</string>
<!-- Min -->
<string name="min_elevation_label">Niedrigste Höhenlage</string>
<string name="min_grade_label">Minimale Steigung</string>
<string name="min_pace_label">Minimaler Pace</string>
<!-- Max -->
<string name="max_elevation_label">Höchste Höhenlage</string>
<string name="max_grade_label">Maximale Steigung</string>
<string name="max_speed_label">Max. Geschwindigkeit</string>
<string name="loading_track">Lade Track…</string>
<!-- Chart Settings -->
<string name="chart_settings">Diagramm-Einstellungen</string>
<!-- X Axis -->
<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>
<!-- Units -->
<!-- Time -->
<string name="hour">Stunde</string>
<string name="hours">Stunden</string>
<string name="hr">Std</string>
<string name="minute">Minute</string>
<string name="minutes">Minuten</string>
<string name="second">Sekunde</string>
<string name="seconds">Sekunden</string>
<!-- Distance -->
<string name="feet">Fuß</string>
<string name="kilometers_long">Kilometer</string>
<string name="miles_long">Meilen</string>
<!-- Speed and Pace -->
<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>
<!-- Stats Labels -->
<string name="moving_time_label">Zeit in Bewegung</string>
<string name="total_time_label">Gesamtzeit</string>
<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">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 -->
<string name="eula_title">Lizenzvereinbarung für Endverbraucher</string>
<string name="accept">Akzeptieren</string>
<string name="decline">Ablehnen</string>
<!-- About -->
<string name="about">Über</string>
<string name="help">Hilfe</string>
<string name="about_email">Unterstützung</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">Senden an…</string>
<string name="sendtogoogle_send_now">Jetzt senden</string>
<string name="sendtogoogle_create_new_map">Neue Karte erstellen</string>
<string name="sendtogoogle_pick_existing_map">Eine Karte auswählen</string>
<string name="sendtogoogle_send_stats">Trackstatistik senden</string>
<string name="sendtogoogle_send_stats_and_points">Statistik und Punkte senden</string>
<!-- 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ähle ein Konto</string>
<!-- SD card -->
<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">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>
<string name="welcome_about">Über My Tracks</string>
<!-- Announcement -->
<string name="announce_no_distance">My Tracks keine Entfernung</string>
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_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">Durchschnitts-Pace</string>
<string name="elevation_chart_name">Höhenprofil</string>
<string name="elevation_gain_label">Höhengewinn</string>
<string name="elevation_label">Höhe</string>
<string name="latitude_label">Breitengrad</string>
<string name="longitude_label">Längengrad</string>
<!-- Min -->
<string name="min_elevation_label">Niedrigste Höhenlage</string>
<string name="min_grade_label">Minimale Steigung</string>
<string name="min_pace_label">Minimaler Pace</string>
<!-- Max -->
<string name="max_elevation_label">Höchste Höhenlage</string>
<string name="max_grade_label">Maximale Steigung</string>
<string name="max_speed_label">Max. Geschwindigkeit</string>
<string name="loading_track">Lade Track…</string>
<!-- Chart Settings -->
<string name="chart_settings">Diagramm-Einstellungen</string>
<!-- X Axis -->
<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>
<!-- Units -->
<!-- Time -->
<string name="hour">Stunde</string>
<string name="hours">Stunden</string>
<string name="hr">Std</string>
<string name="minute">Minute</string>
<string name="minutes">Minuten</string>
<string name="second">Sekunde</string>
<string name="seconds">Sekunden</string>
<!-- Distance -->
<string name="feet">Fuß</string>
<string name="kilometers_long">Kilometer</string>
<string name="miles_long">Meilen</string>
<!-- Speed and Pace -->
<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>
<!-- Stats Labels -->
<string name="moving_time_label">Zeit in Bewegung</string>
<string name="total_time_label">Gesamtzeit</string>
<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">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 -->
<string name="eula_title">Lizenzvereinbarung für Endverbraucher</string>
<string name="accept">Akzeptieren</string>
<string name="decline">Ablehnen</string>
<!-- About -->
<string name="about">Über</string>
<string name="help">Hilfe</string>
<string name="about_email">Unterstützung</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">Senden an…</string>
<string name="sendtogoogle_send_now">Jetzt senden</string>
<string name="sendtogoogle_send_stats">Trackstatistik senden</string>
<string name="sendtogoogle_send_stats_and_points">Statistik und Punkte senden</string>
<!-- 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ähle ein Konto</string>
<!-- SD card -->
<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">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>
<string name="welcome_about">Über My Tracks</string>
<!-- Announcement -->
<string name="announce_no_distance">My Tracks keine Entfernung</string>
<!-- Format:
$1 : Distance label
$2 : Distance value (5)
@@ -400,42 +380,42 @@ limitations under the License.
$4 : Time description (10 minutes 5 seconds)
$5 : Speed value (5.6) : Pace value (5 minutes 6 seconds)
$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>
-->
<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>
+3 -24
View File
@@ -51,7 +51,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Su recorrido ha sido enviado a maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Su recorrido ha sido enviado a una nueva hoja de cálculo en docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Su recorrido ha sido enviado a docs.google.com.</string>
<string name="status_mymap_info">A veces nuevos recorridos pueden tardar algunos minutos en aparecer.</string>
<string name="status_enable_gps">Por favor presione aquí para ir al menú de Seguridad y Ubicación y asegúrese que el GPS esté habilitado. Presione el botón de regreso para regresar.</string>
<string name="status_approximate_location">Nota: Una ubicación aproximada es mostrada, basada en informacion de torres de telefonía celular y red WIFI.</string>
<string name="status_no_location">La ubicación actual no puede ser determinada. Habilite más fuentes de datos (vea configuraciones de Seguridad y Ubicación) y asegúrese que el cielo sea visible. Inténtelo mas tarde.</string>
@@ -98,6 +97,7 @@ limitations under the License.
<string name="yes">Sí</string>
<string name="success">Éxito</string>
<string name="waypoint">Señal de recorrido</string>
<string name="cancel">Cancelar</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Inicio)</string>
@@ -193,30 +193,10 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satélites GPS</string>
<!-- My Maps -->
<string name="select_map_message">Elija un mapa de su cuenta:</string>
<string name="create_new_map">Crear mapa nuevo...</string>
<string name="map_list_title">Enviar a Google My Maps</string>
<string name="maps_list_loading">Cargando mapas...</string>
<string name="maps_list_failed">Sus mapas no pudieron ser cargados en este momento!</string>
<string name="open_map">Seleccione este mapa</string>
<string name="edit_map_details">Editar detalles del mapa</string>
<string name="share_map">Compartir mapa con amigos</string>
<string name="share_track">Compartir recorrido con amigos</string>
<string name="delete_map">Borrar mapa de su cuenta</string>
<string name="map_title">Título del mapa:</string>
<string name="map_description">Descripción del mapa:</string>
<string name="map_searchable_edit">Permitir que este mapa y su contenido sean mostrados en resultados de búsqueda</string>
<string name="map_not_searchable_edit">No permitir que este mapa y su contenido sean mostrados en resultados de búsqueda, pero permitir acceso directo mediante su URL</string>
<string name="map_searchable">Este mapa y su contenido serán mostrados en resultados de búsqueda</string>
<string name="map_not_searchable">No mostrar ni este mapa ni sus contenidos en resultados de búsqueda, pero permitir acceso directo mediante su URL</string>
<string name="close">Cerrar</string>
<string name="edit">Editar</string>
<string name="cancel">Cancelar</string>
<string name="save">Guardar</string>
<!-- Sharing -->
<string name="share_map">Compartir mapa con amigos</string>
<string name="share_map_subject">Me gustaría compartir un mapa contigo</string>
<string name="share_map_body_format">Creo que este mapa pudiera interesarte:
@@ -225,6 +205,7 @@ limitations under the License.
<string name="send_track_body_format">Creo que este recorrido pudiera interesarte.
Para ver este recorrido usa Google Earth o algún otro software de mapeo.</string>
<string name="share_track">Compartir recorrido con amigos</string>
<!-- Visibility -->
<string name="public_map">Público</string>
@@ -315,8 +296,6 @@ limitations under the License.
<!-- Send to Google -->
<string name="sendtogoogle_title">Enviar a...</string>
<string name="sendtogoogle_send_now">Enviar ahora</string>
<string name="sendtogoogle_create_new_map">Crear un nuevo mapa</string>
<string name="sendtogoogle_pick_existing_map">Escoja un mapa existente</string>
<string name="sendtogoogle_send_stats">Enviar estadísticas de recorrido</string>
<string name="sendtogoogle_send_stats_and_points">Enviar estadísticas y puntos</string>
+6 -23
View File
@@ -53,7 +53,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Votre parcours a été envoyé à maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Votre parcours a été envoyé dans un tableur à docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Votre parcours a été envoyé à docs.google.com.</string>
<string name="status_mymap_info">Votre parcours peut prendre quelques minutes avant d\'être visible.</string>
<string name="status_enable_gps">Cliquez ici et allez dans \'Paramètres\', \'Sécurité et Localisation\' et selectionnez \'Activer les satellites GPS\'. Appuyez sur le bouton de retour pour revenir au menu.</string>
<string name="status_approximate_location">Note : Votre position est approximative, elle a été déterminée par les antennes de téléphonie ou le réseau WiFi.</string>
<string name="status_no_location">Votre position ne peut être déterminée. Vérifiez votre configuration dans \'Paramètres\', \'Sécurité et Localisation\'. Assurez-vous également que le ciel soit bien visible.</string>
@@ -100,6 +99,10 @@ limitations under the License.
<string name="yes">Oui</string>
<string name="error">Erreur</string>
<string name="waypoint">Point de passage</string>
<string name="cancel">Annuler</string>
<string name="close">Fermer</string>
<string name="edit">Modifier</string>
<string name="save">Enregistrer</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Début)</string>
@@ -194,29 +197,8 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satellites GPS</string>
<!-- MyMaps -->
<string name="select_map_message">Choisissez une carte de votre compte :</string>
<string name="create_new_map">Création d\'une nouvelle carte en cours...</string>
<string name="map_list_title">Envoyer à Google Mes cartes</string>
<string name="maps_list_loading">Chargements de vos cartes...</string>
<string name="maps_list_failed">Vos cartes n\'ont pu être chargées</string>
<string name="open_map">Sélectionnez la carte</string>
<string name="edit_map_details">Consulter les détails de la carte</string>
<string name="share_map">Partager.</string>
<string name="share_track">Partager des parcours.</string>
<string name="delete_map">Supprimer la carte de votre compte.</string>
<string name="map_title">Titre de la carte :</string>
<string name="map_description">Description de la carte :</string>
<string name="map_searchable_edit">Rendre cette carte et son contenu visibles dans les résultats de recherche.</string>
<string name="map_not_searchable_edit">Ne pas inclure cette carte et sont contenu dans les résultats de recherche. Ils seront cependant consultables par d\'autres utilisateurs en utilisant l\'adresse URL exacte</string>
<string name="map_searchable">Cette carte et son contenu seront visibles dans les résultats de recherche</string>
<string name="map_not_searchable">Ni cette carte, ni son contenu seront visible dans les résultats de recherche, mais ils seront consultables en utilisant l\'adresse URL exacte</string>
<string name="close">Fermer</string>
<string name="edit">Modifier</string>
<string name="cancel">Annuler</string>
<string name="save">Enregistrer</string>
<!-- Sharing -->
<string name="share_map">Partager.</string>
<string name="share_map_subject">Je voudrais partager ma carte avec vous</string>
<string name="share_map_body_format">Je pense que cette carte vous intéressera :
@@ -225,6 +207,7 @@ limitations under the License.
<string name="send_track_body_format">Je pense que ce parcours vous intéressera.
Pour voir ce parcours, utilisez Google Earth ou tout autre logiciel de cartographie.</string>
<string name="share_track">Partager des parcours.</string>
<!-- Visibility -->
<string name="unlisted_map">Carte non listée</string>
+5 -23
View File
@@ -54,7 +54,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Il tuo percorso è stato inviato a maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Il tuo percorso è stato inviato ad un nuovo foglio di calcolo su docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Il tuo percorso è stato inviato a docs.google.com.</string>
<string name="status_mymap_info">Potrebbe essere necessario aspettare qualche minuto prima che i nuovi percorsi siano visibili.</string>
<string name="status_enable_gps">Premi qui per andare a «Impostazioni di posizione e protezione» e attiva «Utilizza satelliti GPS». Poi premi il tasto di ritorno per tornare qui.</string>
<string name="status_approximate_location">Nota: la posizione indicata è un\'approssimazione determinata in base alle antenne cellulari e reti WIFI vicine.</string>
<string name="status_no_location">La posizione non può essere determinata. Attiva più fonti di posizione (vedi «Impostazioni di posizione e protezione») e eccertati che il cielo sia visibile. Riprova più tardi.</string>
@@ -104,6 +103,10 @@ limitations under the License.
<string name="error">Errore</string>
<string name="success">Successo</string>
<string name="waypoint">Punto di passaggio</string>
<string name="cancel">Annulla</string>
<string name="close">Chiudi</string>
<string name="edit">Modifica</string>
<string name="save">Salva</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Inizio)</string>
@@ -200,28 +203,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satelliti GPS</string>
<!-- My Maps -->
<string name="select_map_message">Scegli una mappa dal tuo account:</string>
<string name="create_new_map">Crea una nuova mappa...</string>
<string name="map_list_title">Invia a «Le mie mappe» di Google</string>
<string name="maps_list_loading">Caricamento mappe in corso...</string>
<string name="maps_list_failed">Le tue mappe non possono momentaneamente essere caricate!</string>
<string name="open_map">Scegli questa mappa</string>
<string name="edit_map_details">Modifica dettagli della mappa</string>
<string name="share_map">Condividi la mappa con amici</string>
<string name="share_track">Condividi il percorso con amici</string>
<string name="delete_map">Elimina la mappa dal tuo account</string>
<string name="map_title">Titolo della mappa:</string>
<string name="map_description">Descrizione della mappa:</string>
<string name="map_searchable_edit">Rendi questa mappa e il suo contenuto visibili fra i risultati di ricerche</string>
<string name="map_not_searchable_edit">Rendi questa mappa e il suo contenuto invisibili fra i risultati di ricerche. Resteranno comunque visibili ad altri utilizzando l\'indirizzo URL esatto</string>
<string name="map_searchable">Questa mappa e il suo contenuto saranno visibili fra i risultati di ricerche</string>
<string name="map_not_searchable">Né questa mappa né il suo contenuto saranno visibili in risultati di ricerche, ma saranno comunque visibili ad altri tramite un link diretto.</string>
<string name="close">Chiudi</string>
<string name="edit">Modifica</string>
<string name="cancel">Annulla</string>
<string name="save">Salva</string>
<!-- Sharing -->
<string name="share_map_subject">Vorrei condividere una mappa con te</string>
<string name="share_map_body_format">Credo che questa mappa potrebbe interessarti:
@@ -231,6 +212,7 @@ limitations under the License.
<string name="send_track_body_format">Credo che questo percorso potrebbe interessarti.
Per vedere questo percorso, usa Google Earth o un altro software di cartografia.</string>
<string name="share_track">Condividi il percorso con amici</string>
<!-- Visibility -->
<string name="public_map">Pubblica</string>
+5 -25
View File
@@ -52,7 +52,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">トラックを maps.google.com に送信しました.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">トラックを docs.google.com の新しいスプレッドシートに送信しました.</string>
<string name="status_tracks_have_been_uploaded_to_docs">トラックを docs.google.com に送信しました.</string>
<string name="status_mymap_info">新しいトラックが見えるようになるまで数分程度かかります.</string>
<string name="status_enable_gps">ここをタップして,セキュリティ &amp;位置情報 を開いて,「GPS機能をONにする」にチェックして下さい.バックボタンで戻ってください.</string>
<string name="status_approximate_location">ご注意: 携帯電話中継局とWi-Fiネットワークから大まかな位置を特定します.</string>
<string name="status_no_location">現在位置決定ができません.位置取得源を増やし(セキュリティ&amp;位置情報をご覧ください),空が見えるようにして下さい.もう一度試して下さい.</string>
@@ -102,6 +101,10 @@ limitations under the License.
<string name="error">エラー</string>
<string name="success">成功</string>
<string name="waypoint">ウェイポイント</string>
<string name="cancel">キャンセル</string>
<string name="close">閉じる</string>
<string name="edit">編集</string>
<string name="save">保存</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(開始)</string>
@@ -201,30 +204,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS衛星</string>
<!-- My Maps -->
<string name="select_map_message">ご使用のアカウントからのマップ選択:</string>
<string name="create_new_map">新規マップ...</string>
<string name="map_list_title">Googleマイマップへの送信</string>
<string name="maps_list_loading">マップの読み込み中...</string>
<string name="maps_list_failed">マップを読み込めませんでした!</string>
<string name="open_map">このマップを選択</string>
<string name="edit_map_details">マップ詳細の編集</string>
<string name="share_map">マップの共有</string>
<string name="share_track">トラックの共有</string>
<string name="delete_map">アカウントからマップの削除</string>
<string name="map_title">マップタイトル:</string>
<string name="map_description">マップ説明:</string>
<string name="map_searchable_edit">このマップとコンテンツを検索結果に表示させる</string>
<string name="map_not_searchable_edit">このマップとコンテンツはどちらも検索結果に表示させない(直接URLで他の方が見ることは可能)</string>
<string name="map_searchable">このマップとコンテンツは検索結果に表示されます</string>
<string name="map_not_searchable">このマップとコンテンツはどちらも検索結果に表示されませんが,直接リンクで他の方が見ることができます.</string>
<string name="close">閉じる</string>
<string name="edit">編集</string>
<string name="cancel">キャンセル</string>
<string name="save">保存</string>
<!-- Sharing -->
<string name="share_map_subject">マップを共有したいと思います</string>
<string name="share_map_body_format">このマップに興味を持って頂けると思います:
@@ -234,6 +213,7 @@ limitations under the License.
<string name="send_track_body_format">このトラックに興味を持って頂けると思います.
このトラックを閲覧するには,Google Earthまたは他のマッピングソフトウェアが必要です.</string>
<string name="share_track">トラックの共有</string>
<!-- Visibility -->
<string name="public_map">公開</string>
+5 -23
View File
@@ -53,7 +53,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Twoja trasa został wysłana do maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Twoja trasa została wysłana do nowego arkusza na docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Twoja trasa została wysłana do docs.google.com.</string>
<string name="status_mymap_info">Trasa będzie widoczna za kilka minut.</string>
<string name="status_enable_gps">Tapnij tutaj, aby przejść do ustawień Lokalizacji i zabezpieczeń, a następnie zaznacz Użyj satelitów GPS. Naciśnij klawisz Wstecz, aby powrócić.</string>
<string name="status_approximate_location">Uwaga: używana jest przybliżona lokalizacja ustalona za pomocą stacji komórkowych i sieci WIFI.</string>
<string name="status_no_location">Lokalizacja nie może być obecnie ustalona. Włącz wiecej źródeł (zobacz ustawienia Lokalizacji i zabezpieczeń) i upewnij się, że niebo jest widoczne. Spróbuj później.</string>
@@ -103,6 +102,10 @@ limitations under the License.
<string name="error">Błąd</string>
<string name="success">Powodzenie</string>
<string name="waypoint">Punkt orientacyjny</string>
<string name="cancel">Anuluj</string>
<string name="close">Zamknij</string>
<string name="edit">Edytuj</string>
<string name="save">Zapisz</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Początek)</string>
@@ -233,28 +236,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satelity GPS</string>
<!-- My Maps -->
<string name="select_map_message">Wybierz mapę z twojego konta:</string>
<string name="create_new_map">Utwórz nową mapę...</string>
<string name="map_list_title">Wyślij do Moich Map Google</string>
<string name="maps_list_loading">Wczytywanie map...</string>
<string name="maps_list_failed">Nie można wczytać twoich map!</string>
<string name="open_map">Wybierz tę mapę</string>
<string name="edit_map_details">Edytuj szczegóły mapy</string>
<string name="share_map">Podziel się mapą z przyjaciółmi</string>
<string name="share_track">Podziel się trasą z przyjaciółmi</string>
<string name="delete_map">Usuń mapę z twojego konta</string>
<string name="map_title">Tytuł mapy:</string>
<string name="map_description">Opis mapy:</string>
<string name="map_searchable_edit">Pokazuj tę pamę i jej zawartość w wynikach wyszukiwania</string>
<string name="map_not_searchable_edit">Nie pokazuj tej mapy i jej zawartości w wynikach wyszukiwania. Dostęp do mapy będzie ciągle możliwy poprzez odnośnik</string>
<string name="map_searchable">Ta mapa i jej zawartość będzie dostępna w wynikach wyszukiwania</string>
<string name="map_not_searchable">Mapa i jej zawartość nie będzie dostępna w wynikach wyszukiwania, ale będzie dostępna przez bezpośredni odnośnik</string>
<string name="close">Zamknij</string>
<string name="edit">Edytuj</string>
<string name="cancel">Anuluj</string>
<string name="save">Zapisz</string>
<!-- Sharing -->
<string name="share_map_subject">Chciałbym się z Tobą podzielić mapą</string>
<string name="share_map_body_format">Myślę, że może Cię zainteresować ta mapa:
@@ -264,6 +245,7 @@ limitations under the License.
<string name="send_track_body_format">Myślę, że może Cię zainteresować ta trasa.
Trasę możesz obejrzeć w Google Earth lub innym programie do obsługi map.</string>
<string name="share_track">Podziel się trasą z przyjaciółmi</string>
<!-- Visibility -->
<string name="public_map">Publiczna</string>
+5 -23
View File
@@ -52,7 +52,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Seu caminho foi enviado para maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Seu caminho foi enviado para uma nova planilha em docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Seu caminho foi enviado para docs.google.com.</string>
<string name="status_mymap_info">"Pode demorar alguns minutor para que novos caminhos apareçam.</string>
<string name="status_enable_gps">"Por favor pressione aqui para ir aos ajustes de segurança e localização e verifique que o GPS esteja habilitado. Pressione o botão de voltar para retornar."</string>
<string name="status_approximate_location">Nota: Uma localização aproximada, determinada por torres de telefonia celular, está sendo mostrada.</string>
<string name="status_no_location">Sua localização não pôde ser determinada. Habilite fontes adicionais de localização (veja configurações de Segurança e Localização) e assegure-se de que o céu está visível.</string>
@@ -101,6 +100,10 @@ limitations under the License.
<string name="error">Erro</string>
<string name="success">Successo</string>
<string name="waypoint">Ponto de passagem</string>
<string name="cancel">Cancelar</string>
<string name="close">Fechar</string>
<string name="edit">Editar</string>
<string name="save">Salvar</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Início)</string>
@@ -184,28 +187,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">Satélites de GPS</string>
<!-- My Maps -->
<string name="select_map_message">Escolha um mapa da sua conta:</string>
<string name="create_new_map">Criar novo mapa...</string>
<string name="map_list_title">Enviar para o Google Meus Mapas</string>
<string name="maps_list_loading">Carregando mapas...</string>
<string name="maps_list_failed">Seus mapas não puderam ser carregados neste momento!</string>
<string name="open_map">Selecionar este Mapa</string>
<string name="edit_map_details">Editar detalhes do mapa</string>
<string name="share_map">Compartilhar mapa com amigos</string>
<string name="share_track">Compartilhar caminho com amigos</string>
<string name="delete_map">Apagar mapa da sua conta</string>
<string name="map_title">Título do Mapa:</string>
<string name="map_description">Descrição do Mapa:</string>
<string name="map_searchable_edit">Fazer com que o conteúdo deste mapa apareça em resultados de buscas</string>
<string name="map_not_searchable_edit">Não permitir que este mapa ou seu conteúdo apareçam em resultados de buscas, porém ele ainda pode ser visto por sua URL direta</string>
<string name="map_searchable">Este mapa e seu conteúdo vão aparecer em resultados de buscas</string>
<string name="map_not_searchable">Nem este mapa nem seu conteúdo vão aparecer em resultados de buscas, porém outras pessoas ainda podem vê-los se obtiverem sua URL direta</string>
<string name="close">Fechar</string>
<string name="edit">Editar</string>
<string name="cancel">Cancelar</string>
<string name="save">Salvar</string>
<!-- Sharing -->
<string name="share_map_subject">Eu gostaria de compartilhar um mapa com você</string>
<string name="share_map_body_format">Eu acredito que você pode se interessar por este mapa:
@@ -215,6 +196,7 @@ limitations under the License.
<string name="send_track_body_format">Eu acredito que você pode se interesasr por este caminho.
Para visualizar este caminho, use o Google Earth ou outro software de mapas.</string>
<string name="share_track">Compartilhar caminho com amigos</string>
<!-- Visibility -->
<string name="public_map">Público</string>
+5 -23
View File
@@ -53,7 +53,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Ditt spår har skickats till maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Ditt spår har skickats till ett nytt kalkylblad på docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Ditt spår har skickats till docs.google.com.</string>
<string name="status_mymap_info">Det kan ibland ta några minuter innan nya spår visas.</string>
<string name="status_enable_gps">Klicka här för att gå till Säkerhet och Platsinställningar och kryssa i \'aktivera gps-satteliter\'. Tryck på tillbakaknappen för att återgå.</string>
<string name="status_approximate_location">OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
<string name="status_no_location">En position kan för närvarande inte fastställas. Aktivera mer platskällor (se Säkerhet och Platsinställningar) och se till att himlen är synlig. Försök igen senare.</string>
@@ -103,6 +102,10 @@ limitations under the License.
<string name="error">Fel</string>
<string name="success">Lyckades</string>
<string name="waypoint">Positionsmärke</string>
<string name="cancel">Avbryt</string>
<string name="close">Stäng</string>
<string name="edit">Ändra</string>
<string name="save">Spara</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Start)</string>
@@ -202,28 +205,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS-satelliter</string>
<!-- My Maps -->
<string name="select_map_message">Välj en karta från ditt konto:</string>
<string name="create_new_map">Skapa en ny karta...</string>
<string name="map_list_title">Skicka till Google Mina Kartor</string>
<string name="maps_list_loading">Laddar kartor...</string>
<string name="maps_list_failed">Dina kartor kunde inte laddas just nu!</string>
<string name="open_map">Välj den här kartan</string>
<string name="edit_map_details">Ändra kartdetaljer</string>
<string name="share_map">Dela kartan med vänner</string>
<string name="share_track">Dela spår med vänner</string>
<string name="delete_map">Ta bort kartan från ditt konto</string>
<string name="map_title">Karttitel:</string>
<string name="map_description">Kartbeskrivning:</string>
<string name="map_searchable_edit">Gör så att kartan och dess innehåll visas i sökresultat</string>
<string name="map_not_searchable_edit">Låt inte kartan och dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk</string>
<string name="map_searchable">Kartan och dess innehåll kommer att visas i sökresultat</string>
<string name="map_not_searchable">Varken kartan eller dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk</string>
<string name="close">Stäng</string>
<string name="edit">Ändra</string>
<string name="cancel">Avbryt</string>
<string name="save">Spara</string>
<!-- Sharing -->
<string name="share_map_subject">Jag vill dela en karta med dig</string>
<string name="share_map_body_format">Jag tror att du skulle vara intresserad av den här kartan:
@@ -233,6 +214,7 @@ limitations under the License.
<string name="send_track_body_format">Jag tror att du skulle vara intresserad av det här spåret.
För att se det här spåret använd Google Earth eller något annat kartprogram.</string>
<string name="share_track">Dela spår med vänner</string>
<!-- Visibility -->
<string name="public_map">Publik</string>
+5 -23
View File
@@ -56,7 +56,6 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">你的轨迹已经发送到 maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">你的轨迹已经作为新的电子表格发送到 docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">你的轨迹已经发送到 docs.google.com.</string>
<string name="status_mymap_info">新增的轨迹可能需要等待一段时间才会显示到页面上.</string>
<string name="status_enable_gps">请点击这里进入\'安全和位置设置\'并勾选上\'启用 GPS 卫星\'. 然后用后退键返回.</string>
<string name="status_approximate_location">注意: 当前显示的位置是根据无线信号和无线网络计算的近似位置.</string>
<string name="status_no_location">目前无法定位. 请启用更多的位置来源 (在\'安全和位置设置\'中) 并确认天空是可见的. 再重试.</string>
@@ -106,6 +105,10 @@ limitations under the License.
<string name="error">错误</string>
<string name="success">成功</string>
<string name="waypoint">路点</string>
<string name="cancel">取消</string>
<string name="close">关闭</string>
<string name="edit">编辑</string>
<string name="save">保存</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(开始)</string>
@@ -239,28 +242,6 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS 卫星</string>
<!-- My Maps -->
<string name="select_map_message">从你的帐户里选择地图:</string>
<string name="create_new_map">创建新地图...</string>
<string name="map_list_title">发送到 Google My Maps</string>
<string name="maps_list_loading">载入地图中...</string>
<string name="maps_list_failed">现在无法载入你的地图!</string>
<string name="open_map">选择此地图</string>
<string name="edit_map_details">编辑地图详细信息</string>
<string name="share_map">与朋友分享地图</string>
<string name="share_track">与朋友分享轨迹</string>
<string name="delete_map">删除你帐号下的此地图</string>
<string name="map_title">地图标题:</string>
<string name="map_description">地图描述:</string>
<string name="map_searchable_edit">允许此地图及其内容出现在搜索结果中</string>
<string name="map_not_searchable_edit">不允许地图及其内容出现在搜索结果中, 但它的内容依然可以通过网址直接访问</string>
<string name="map_searchable">此地图及其内容会出现在搜索结果中</string>
<string name="map_not_searchable">此地图及其内容不会出现在搜索结果中, 但它的内容依然可以通过网址直接访问</string>
<string name="close">关闭</string>
<string name="edit">编辑</string>
<string name="cancel">取消</string>
<string name="save">保存</string>
<!-- Sharing -->
<string name="share_map_subject">我想与你分享一张地图</string>
<string name="share_map_body_format">我想你可能会对这个地图感兴趣:
@@ -270,6 +251,7 @@ limitations under the License.
<string name="send_track_body_format">我想你可能会对这条轨迹感兴趣.
请用 Google Earth 或其它地图软件查看此轨迹.</string>
<string name="share_track">与朋友分享轨迹</string>
<!-- Visibility -->
<string name="public_map">公开</string>
+22 -42
View File
@@ -35,7 +35,7 @@ limitations under the License.
<!-- Error messages -->
<string name="error_no_location_manager">No location manager was found on this device!</string>
<string name="error_no_gps_location_provider">No GPS location provider was found on this device.</string>
<string name="error_sending_to_mymap">There was an error while sending your track to Google My Map. Please try again later.</string>
<string name="error_sending_to_fusiontables">There was an error while sending your track to Google Fusion Tables. Please try again later.</string>
<string name="error_sending_to_docs">There was an error while sending your track to Google Docs. Please try again later.</string>
<string name="error_unable_to_start_recording">Unable to start recording. Try again.</string>
<string name="error_unable_to_stop_recording">Unable to stop recording. Try again.</string>
@@ -50,11 +50,10 @@ limitations under the License.
<string name="wait_for_fix">Waiting for a better GPS fix. Make sure the sky is visible.</string>
<string name="status_now_recording">Now recording a new track.</string>
<string name="status_stopped_recording">Stopped recording your track.</string>
<string name="status_new_mymap_has_been_created">A new Google My Map has been created at maps.google.com.</string>
<string name="status_new_fusiontable_has_been_created">A new Google Fusion table has been created at google.com/fusiontables.</string>
<string name="status_tracks_have_been_uploaded">Your track has been sent to maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Your track has been sent to a new spreadsheet at docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Your track has been sent to docs.google.com.</string>
<string name="status_mymap_info">It may sometimes take a few minutes before new tracks appear.</string>
<string name="status_enable_gps">Please tap here to go to Security and Location Settings and check \'enable GPS satellites\'. Press back button to return.</string>
<string name="status_approximate_location">Note: An approximate location as determined from cell towers and WIFI networks is shown.</string>
<string name="status_no_location">A location can currently not be determined. Enable more location sources (see Security and Location Settings) and make sure the sky is visible. Try again later.</string>
@@ -66,11 +65,11 @@ limitations under the License.
<string name="progress_message">Sending your track to Google.</string>
<string name="progress_message_reading_track">Reading track...</string>
<string name="progress_message_preparing_track">Preparing track...</string>
<string name="progress_message_authenticating_mymaps">Authenticating to Google Maps...</string>
<string name="progress_message_authenticating_fusiontables">Authenticating to Google Fusion Tables...</string>
<string name="progress_message_authenticating_docs">Authenticating to Google Docs...</string>
<string name="progress_message_sending_docs">Sending track to Google Docs...</string>
<string name="progress_message_sending_mymaps">Sending track to Google Maps...</string>
<string name="progress_message_creating_map">Creating new My Map...</string>
<string name="progress_message_sending_fusiontables">Sending track to Fusion Tables...</string>
<string name="progress_message_creating_fusiontable">Creating new Fusion table...</string>
<string name="import_progress_message">Importing a GPS track. This may take a while.</string>
<string name="write_progress_message">Writing file to SD card. This may take a while.</string>
<string name="progress_message_updating_track">Updating track statistics...</string>
@@ -81,10 +80,9 @@ limitations under the License.
<string name="track_will_be_permanently_deleted">The track and its markers will be permanently deleted from this phone.</string>
<string name="marker_will_be_permanently_deleted">The marker will be permanently deleted from this phone.</string>
<!-- Text and links when uploading to Google My Maps -->
<!-- Text and links when uploading to Google Fusion Tables -->
<string name="new_map_description">Created by My Tracks on Android.</string>
<string name="my_map_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<string name="new_map_title">New map</string>
<string name="fusiontable_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">Points</string>
@@ -105,6 +103,10 @@ limitations under the License.
<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>
<string name="save">Save</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Start)</string>
@@ -112,8 +114,8 @@ limitations under the License.
<!-- TrackList -->
<string name="tracklist_show_track">Show</string>
<string name="tracklist_send_to_mymaps">Send to Google My Maps</string>
<string name="tracklist_send_to_new_mymap">Send to New Google My Map</string>
<string name="tracklist_send_to_fusiontables">Send to Google Fusion Tables</string>
<string name="tracklist_send_to_new_fusiontable">Send to New Google Fusion Table</string>
<string name="tracklist_send_to_docs">Send to Google Docs</string>
<string name="tracklist_send_to_google">Send to Google...</string>
<string name="tracklist_delete_track">Delete from phone...</string>
@@ -123,7 +125,7 @@ limitations under the License.
<string name="tracklist_save_as_kml">Save a KML file</string>
<string name="tracklist_save_as_csv">Save a CSV file</string>
<string name="tracklist_save_as_tcx">Save a TCX file</string>
<string name="tracklist_btn_send_all_to_mymaps">Send to Google My Maps</string>
<string name="tracklist_btn_send_all_to_fusiontables">Send to Google Fusion Tables</string>
<string name="tracklist_btn_delete_all">Delete all tracks</string>
<!-- Export All -->
<string name="tracklist_btn_export_all">Export all tracks</string>
@@ -132,7 +134,7 @@ limitations under the License.
<string name="tracklist_all_tracks">All Tracks:</string>
<string name="tracklist_this_track">This Track</string>
<string name="tracklist_share_track">Share with Friends...</string>
<string name="tracklist_share_link">A link to Google My Map</string>
<string name="tracklist_share_linkfusion">A link to Google Fusion Tables</string>
<string name="tracklist_share_gpx_file">A GPX file</string>
<string name="tracklist_share_kml_file">A KML file</string>
<string name="tracklist_share_csv_file">A CSV file</string>
@@ -224,6 +226,7 @@ limitations under the License.
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to</string>
<string name="settings_modular_stats_display_title">Modular Stats Display</string>
<!-- Backups -->
<string name="settings_backup">Backup (BETA)</string>
<string name="settings_sd_backup">SD Card Backup</string>
<string name="settings_backup_to_sd">Backup now</string>
@@ -232,7 +235,6 @@ limitations under the License.
<string name="settings_restore_from_sd_summary">Restore all tracks and settings from the SD card</string>
<string name="settings_no_backup_while_recording">Cannot manipulate backups while recording a track.</string>
<!-- Backups -->
<string name="restore_overwrites_warning">Restoring a backup from the SD card will OVERWRITE all your current tracks and settings. Are you sure?</string>
<string name="select_backup_to_restore">Select the backup to restore</string>
<string name="no_backups">No backups found.</string>
@@ -242,37 +244,17 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS Satellites</string>
<!-- My Maps -->
<string name="select_map_message">Pick a map from your account:</string>
<string name="create_new_map">Create new map...</string>
<string name="map_list_title">Send to Google My Maps</string>
<string name="maps_list_loading">Loading maps...</string>
<string name="maps_list_failed">Your maps could not be loaded at this time!</string>
<string name="open_map">Select this Map</string>
<string name="edit_map_details">Edit Map Details</string>
<string name="share_map">Share Map with Friends</string>
<string name="share_track">Share Track with Friends</string>
<string name="delete_map">Delete Map from your Account</string>
<string name="map_title">Map Title:</string>
<string name="map_description">Map Description:</string>
<string name="map_searchable_edit">Make this map and its contents show up in search results</string>
<string name="map_not_searchable_edit">Do not let this map or its contents show up in search results, though they may still be viewed be others through the direct URL</string>
<string name="map_searchable">This map and its contents will show in search results</string>
<string name="map_not_searchable">Neither this map nor its contents will show in search results, but they may still be viewed by others through a direct link</string>
<string name="close">Close</string>
<string name="edit">Edit</string>
<string name="cancel">Cancel</string>
<string name="save">Save</string>
<!-- Sharing -->
<string name="share_map_subject">I would like to share a map with you</string>
<string name="share_map_body_format">I think you might be interested in this map:
<string name="share_map">Share map with friends</string>
<string name="share_map_subject">I would like to share a track with you</string>
<string name="share_map_body_format">I think you might be interested in this track:
%s</string>
<string name="send_track_subject">I would like to share a track with you</string>
<string name="send_track_body_format">I think you might be interested in this track.
To view this track use Google Earth or other mapping software.</string>
<string name="share_track">Share track with friends</string>
<!-- Visibility -->
<string name="public_map">Public</string>
@@ -346,7 +328,7 @@ limitations under the License.
<string name="min_per_mile">min/mi</string>
<string name="per_mile">per mile</string>
<!-- Power -->
<!-- Sensors -->
<string name="watts">W</string>
<string name="revolutions_per_minute">rpm</string>
@@ -384,11 +366,9 @@ limitations under the License.
<!-- Send to Google -->
<string name="sendtogoogle_title">Send to...</string>
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
<string name="sendtogoogle_google_fusiontables">Google Fusion Tables</string>
<string name="sendtogoogle_google_docs">Google Docs</string>
<string name="sendtogoogle_send_now">Send now</string>
<string name="sendtogoogle_create_new_map">Create a new map</string>
<string name="sendtogoogle_pick_existing_map">Pick an existing map</string>
<string name="sendtogoogle_send_stats">Send track statistics</string>
<string name="sendtogoogle_send_stats_and_points">Send statistics and points</string>
@@ -1,32 +0,0 @@
/*
* 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.mymaps;
import com.google.wireless.gdata.maps.MapsClient;
/**
* Constants for My Maps.
*/
public class MyMapsConstants {
public static final String MAPSHOP_BASE_URL =
"http://maps.google.com/maps/ms";
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
/**
* Private constructor to prevent instantiation.
*/
private MyMapsConstants() { }
}
@@ -1,247 +0,0 @@
/*
* 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.mymaps;
import com.google.android.maps.GeoPoint;
import java.io.Serializable;
import java.util.Vector;
/**
* MyMapsFeature contains all of the data associated with a feature in My Maps,
* where a feature is a marker, line, or shape. Some of the data is stored in a
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
* transmitted to other activities.
*/
public class MyMapsFeature implements Serializable {
private static final long serialVersionUID = 8439035544430497236L;
/** A marker feature displays an icon at a single point on the map. */
public static final int MARKER = 0;
/**
* A line feature displays a line connecting a set of points on the map.
*/
public static final int LINE = 1;
/**
* A shape feature displays a border defined by connecting a set of points,
* including connecting the last to the first, and displays the area
* confined by this border.
*/
public static final int SHAPE = 2;
/** The local feature id for this feature, if needed. */
private String androidId;
/**
* The latitudes of the points of this feature in order, specified in
* millionths of a degree north.
*/
private final Vector<Integer> latitudeE6 = new Vector<Integer>();
/**
* The longitudes of the points of this feature in order, specified in
* millionths of a degree east.
*/
private final Vector<Integer> longitudeE6 = new Vector<Integer>();
/** The metadata of this feature in a format efficient for transmission. */
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
/**
* Initializes a valid but empty feature. It will default to a
* {@link #MARKER} with a blue placemark with a dot as an icon at the
* location (0, 0).
*/
public MyMapsFeature() {
}
/**
* Adds a new point to the end of this feature.
*
* @param point The new point to add
*/
public synchronized void addPoint(GeoPoint point) {
latitudeE6.add(point.getLatitudeE6());
longitudeE6.add(point.getLongitudeE6());
}
/**
* Generates a new local id for this feature based on the current time and
* a random number.
*/
public synchronized void generateAndroidId() {
Long time = System.currentTimeMillis();
Integer random = (int) (Math.random() * 10000);
androidId = time.toString() + "." + random.toString();
}
/**
* Retrieves the current local id for this feature if one is available.
*
* @return The local id for this feature
*/
public String getAndroidId() {
return androidId;
}
/**
* Retrieves the current (html) description of this feature. The description
* is stored in the feature metadata.
*
* @return The description of this feature
*/
public String getDescription() {
return featureInfo.getDescription();
}
/**
* Sets the description of this feature. That description is stored in the
* feature metadata.
*
* @param description The new description of this feature
*/
public synchronized void setDescription(String description) {
featureInfo.setDescription(description);
}
/**
* Retrieves the point at the given index for this feature.
*
* @param index The index of the point desired
* @return A {@link GeoPoint} representing the point or null if that point
* doesn't exist
*/
public GeoPoint getPoint(int index) {
if (latitudeE6.size() <= index) {
return null;
}
return new GeoPoint(latitudeE6.get(index), longitudeE6.get(index));
}
/**
* Counts the number of points in this feature and return that count.
*
* @return The number of points in this feature
*/
public int getPointCount() {
return latitudeE6.size();
}
/**
* Retrieves the title of this feature. That title is stored in the feature
* metadata.
*
* @return the current title of this feature
*/
public String getTitle() {
return featureInfo.getTitle();
}
/**
* Retrieves the type of this feature. That type is stored in the feature
* metadata.
*
* @return One of {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
* identifying the type of this feature
*/
public int getType() {
return featureInfo.getType();
}
/**
* Retrieves the current color of this feature as an ARGB color integer.
* That color is stored in the feature metadata.
*
* @return The ARGB color of this feature
*/
public int getColor() {
return featureInfo.getColor();
}
/**
* Retrieves the current line width of this feature. That line width is
* stored in the feature metadata.
*
* @return The line width of this feature
*/
public int getLineWidth() {
return featureInfo.getLineWidth();
}
/**
* Retrieves the current fill color of this feature as an ARGB color
* integer. That color is stored in the feature metadata.
*
* @return The ARGB fill color of this feature
*/
public int getFillColor() {
return featureInfo.getFillColor();
}
/**
* Retrieves the current icon url of this feature. That icon url is stored
* in the feature metadata.
*
* @return The icon url for this feature
*/
public String getIconUrl() {
return featureInfo.getIconUrl();
}
/**
* Sets the title of this feature. That title is stored in the feature
* metadata.
*
* @param title The new title of this feature
*/
public synchronized void setTitle(String title) {
featureInfo.setTitle(title);
}
/**
* Sets the type of this feature. That type is stored in the feature
* metadata.
*
* @param type The new type of the feature. That type must be one of
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
*/
public synchronized void setType(int type) {
featureInfo.setType(type);
}
/**
* Sets the ARGB color of this feature. That color is stored in the feature
* metadata.
*
* @param color The new ARGB color of this feature
*/
public synchronized void setColor(int color) {
featureInfo.setColor(color);
}
/**
* Sets the icon url of this feature. That icon url is stored in the feature
* metadata.
*
* @param url The new icon url of the feature
*/
public synchronized void setIconUrl(String url) {
featureInfo.setIconUrl(url);
}
}
@@ -1,101 +0,0 @@
/*
* 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.mymaps;
/**
* Metadata about a maps feature.
*/
public class MyMapsFeatureMetadata {
private static final String BLUE_DOT_URL =
"http://maps.google.com/mapfiles/ms/micons/blue-dot.png";
private static final int DEFAULT_COLOR = 0x800000FF;
private static final int DEFAULT_FILL_COLOR = 0xC00000FF;
private String title;
private String description;
private int type;
private int color;
private int lineWidth;
private int fillColor;
private String iconUrl;
public MyMapsFeatureMetadata() {
title = "";
description = "";
type = MyMapsFeature.MARKER;
color = DEFAULT_COLOR;
lineWidth = 5;
fillColor = DEFAULT_FILL_COLOR;
iconUrl = BLUE_DOT_URL;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getLineWidth() {
return lineWidth;
}
public void setLineWidth(int width) {
lineWidth = width;
}
public int getFillColor() {
return fillColor;
}
public void setFillColor(int color) {
fillColor = color;
}
public String getIconUrl() {
return iconUrl;
}
public void setIconUrl(String url) {
iconUrl = new String(url);
}
}
@@ -1,195 +0,0 @@
/*
* 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.mymaps;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.StringUtils;
import com.google.wireless.gdata.maps.MapFeatureEntry;
import com.google.wireless.gdata.maps.MapsClient;
import android.graphics.Color;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.StringWriter;
/**
* Converter from GData objects to MyMaps objects.
*/
public class MyMapsGDataConverter {
private final XmlSerializer xmlSerializer;
public MyMapsGDataConverter() throws XmlPullParserException {
xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
}
public static MyMapsMapMetadata getMapMetadataForEntry(
MapFeatureEntry entry) {
MyMapsMapMetadata metadata = new MyMapsMapMetadata();
if ("public".equals(entry.getPrivacy())) {
metadata.setSearchable(true);
} else {
metadata.setSearchable(false);
}
metadata.setTitle(entry.getTitle());
String desc = entry.getContent();
if (desc.length() > 12) {
metadata.setDescription(desc.substring(9, desc.length() - 3));
}
metadata.setGDataEditUri(entry.getEditUri());
return metadata;
}
public static String getMapidForEntry(Entry entry) {
return MapsClient.getMapIdFromMapEntryId(entry.getId());
}
public static Entry getMapEntryForMetadata(MyMapsMapMetadata metadata) {
MapFeatureEntry entry = new MapFeatureEntry();
entry.setEditUri(metadata.getGDataEditUri());
entry.setTitle(metadata.getTitle());
entry.setContent(metadata.getDescription());
entry.setPrivacy(metadata.getSearchable() ? "public" : "unlisted");
entry.setAuthor("android");
entry.setEmail("nobody@google.com");
return entry;
}
public MapFeatureEntry getEntryForFeature(MyMapsFeature feature) {
MapFeatureEntry entry = new MapFeatureEntry();
entry.setTitle(feature.getTitle());
entry.setAuthor("android");
entry.setEmail("nobody@google.com");
entry.setCategoryScheme("http://schemas.google.com/g/2005#kind");
entry.setCategory("http://schemas.google.com/g/2008#mapfeature");
entry.setEditUri("");
if (!StringUtils.isEmpty(feature.getAndroidId())) {
entry.setAttribute("_androidId", feature.getAndroidId());
}
try {
StringWriter writer = new StringWriter();
xmlSerializer.setOutput(writer);
xmlSerializer.startTag(null, "Placemark");
xmlSerializer.attribute(null, "xmlns", "http://earth.google.com/kml/2.2");
xmlSerializer.startTag(null, "Style");
if (feature.getType() == MyMapsFeature.MARKER) {
xmlSerializer.startTag(null, "IconStyle");
xmlSerializer.startTag(null, "Icon");
xmlSerializer.startTag(null, "href");
xmlSerializer.text(feature.getIconUrl());
xmlSerializer.endTag(null, "href");
xmlSerializer.endTag(null, "Icon");
xmlSerializer.endTag(null, "IconStyle");
} else {
xmlSerializer.startTag(null, "LineStyle");
xmlSerializer.startTag(null, "color");
int color = feature.getColor();
// Reverse the color because KML is ABGR and Android is ARGB
xmlSerializer.text(Integer.toHexString(
Color.argb(Color.alpha(color), Color.blue(color),
Color.green(color), Color.red(color))));
xmlSerializer.endTag(null, "color");
xmlSerializer.startTag(null, "width");
xmlSerializer.text(Integer.toString(feature.getLineWidth()));
xmlSerializer.endTag(null, "width");
xmlSerializer.endTag(null, "LineStyle");
if (feature.getType() == MyMapsFeature.SHAPE) {
xmlSerializer.startTag(null, "PolyStyle");
xmlSerializer.startTag(null, "color");
int fcolor = feature.getFillColor();
// Reverse the color because KML is ABGR and Android is ARGB
xmlSerializer.text(Integer.toHexString(Color.argb(Color.alpha(fcolor),
Color.blue(fcolor), Color.green(fcolor), Color.red(fcolor))));
xmlSerializer.endTag(null, "color");
xmlSerializer.startTag(null, "fill");
xmlSerializer.text("1");
xmlSerializer.endTag(null, "fill");
xmlSerializer.startTag(null, "outline");
xmlSerializer.text("1");
xmlSerializer.endTag(null, "outline");
xmlSerializer.endTag(null, "PolyStyle");
}
}
xmlSerializer.endTag(null, "Style");
xmlSerializer.startTag(null, "name");
xmlSerializer.text(feature.getTitle());
xmlSerializer.endTag(null, "name");
xmlSerializer.startTag(null, "description");
xmlSerializer.cdsect(feature.getDescription());
xmlSerializer.endTag(null, "description");
StringBuilder pointBuilder = new StringBuilder();
for (int i = 0; i < feature.getPointCount(); ++i) {
if (i > 0) {
pointBuilder.append('\n');
}
pointBuilder.append(feature.getPoint(i).getLongitudeE6() / 1e6);
pointBuilder.append(',');
pointBuilder.append(feature.getPoint(i).getLatitudeE6() / 1e6);
pointBuilder.append(",0.000000");
}
String pointString = pointBuilder.toString();
if (feature.getType() == MyMapsFeature.MARKER) {
xmlSerializer.startTag(null, "Point");
xmlSerializer.startTag(null, "coordinates");
xmlSerializer.text(pointString);
xmlSerializer.endTag(null, "coordinates");
xmlSerializer.endTag(null, "Point");
} else if (feature.getType() == MyMapsFeature.LINE) {
xmlSerializer.startTag(null, "LineString");
xmlSerializer.startTag(null, "tessellate");
xmlSerializer.text("1");
xmlSerializer.endTag(null, "tessellate");
xmlSerializer.startTag(null, "coordinates");
xmlSerializer.text(pointString);
xmlSerializer.endTag(null, "coordinates");
xmlSerializer.endTag(null, "LineString");
} else {
xmlSerializer.startTag(null, "Polygon");
xmlSerializer.startTag(null, "outerBoundaryIs");
xmlSerializer.startTag(null, "LinearRing");
xmlSerializer.startTag(null, "tessellate");
xmlSerializer.text("1");
xmlSerializer.endTag(null, "tessellate");
xmlSerializer.startTag(null, "coordinates");
xmlSerializer.text(pointString + "\n"
+ Double.toString(feature.getPoint(0).getLongitudeE6() / 1e6)
+ ","
+ Double.toString(feature.getPoint(0).getLatitudeE6() / 1e6)
+ ",0.000000");
xmlSerializer.endTag(null, "coordinates");
xmlSerializer.endTag(null, "LinearRing");
xmlSerializer.endTag(null, "outerBoundaryIs");
xmlSerializer.endTag(null, "Polygon");
}
xmlSerializer.endTag(null, "Placemark");
xmlSerializer.flush();
entry.setContent(writer.toString());
Log.d("My Google Maps", "Generated kml:\n" + entry.getContent());
Log.d("My Google Maps", "Edit URI: " + entry.getEditUri());
} catch (IOException e) {
e.printStackTrace();
}
return entry;
}
}
@@ -1,190 +0,0 @@
/*
* 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.mymaps;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.maps.MapsClient;
import com.google.wireless.gdata.maps.xml.XmlMapsGDataParserFactory;
import com.google.wireless.gdata.parser.xml.SimplePullParser.ParseException;
import com.google.wireless.gdata2.ConflictDetectedException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
/**
* MyMapsGDataWrapper provides a wrapper around GData operations that maintains
* the GData client, and provides a method to run gdata queries with proper
* error handling. After a query is run, the wrapper can be queried about the
* error that occurred.
*/
public class MyMapsGDataWrapper {
/**
* A QueryFunction is passed in when executing a query. The query function
* of the class is called with the GData client as a parameter. The
* function should execute whatever operations it desires on the client
* without concern for whether the client will throw an error.
*/
public interface QueryFunction {
public abstract void query(MapsClient client)
throws AuthenticationException, IOException, ParseException,
ConflictDetectedException, Exception;
}
// The types of error that may be encountered
/** No error occurred. */
public static final int ERROR_NO_ERROR = 0;
/** There was an authentication error, the auth token may be invalid. */
public static final int ERROR_AUTH = 1;
/** There was an internal error on the server side. */
public static final int ERROR_INTERNAL = 2;
/** There was an error connecting to the server. */
public static final int ERROR_CONNECTION = 3;
/** The item queried did not exit. */
public static final int ERROR_NOT_FOUND = 4;
/** There was an error parsing or serializing locally. */
public static final int ERROR_LOCAL = 5;
/** There was a conflict, update the entry and try again. */
public static final int ERROR_CONFLICT = 6;
/**
* A query was run after cleaning up the wrapper, so the client was invalid.
*/
public static final int ERROR_CLEANED_UP = 7;
/** An unknown error occurred. */
public static final int ERROR_UNKNOWN = 100;
private final GDataClient androidGdataClient;
private final MapsClient client;
private String errorMessage;
private int errorType;
private AuthManager auth;
private boolean retryOnAuthFailure;
private int retriesPending;
private boolean cleanupCalled;
public MyMapsGDataWrapper(Context context) {
androidGdataClient = GDataClientFactory.getGDataClient(context);
client =
new MapsClient(androidGdataClient, new XmlMapsGDataParserFactory(
new AndroidXmlParserFactory()));
errorType = ERROR_NO_ERROR;
errorMessage = null;
auth = null;
retryOnAuthFailure = false;
retriesPending = 0;
cleanupCalled = false;
}
public boolean runQuery(final QueryFunction query) {
if (client == null) {
errorType = ERROR_CLEANED_UP;
errorMessage = "GData Wrapper has already been cleaned up!";
return false;
}
try {
query.query(client);
errorType = ERROR_NO_ERROR;
errorMessage = null;
return true;
} catch (AuthenticationException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorType = ERROR_AUTH;
errorMessage = e.getMessage();
} catch (HttpException e) {
Log.e(MyTracksConstants.TAG, "HttpException", e);
errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("401")) {
errorType = ERROR_AUTH;
} else {
errorType = ERROR_CONNECTION;
}
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("503")) {
errorType = ERROR_INTERNAL;
} else {
errorType = ERROR_CONNECTION;
}
} catch (ParseException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorType = ERROR_LOCAL;
errorMessage = e.getMessage();
} catch (ConflictDetectedException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorType = ERROR_CONFLICT;
errorMessage = e.getMessage();
} catch (Exception e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorType = ERROR_UNKNOWN;
errorMessage = e.getMessage();
e.printStackTrace();
}
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
if (errorType == ERROR_AUTH && auth != null) {
Runnable whenFinished = null;
if (retryOnAuthFailure) {
retriesPending++;
whenFinished = new Runnable() {
public void run() {
retriesPending--;
retryOnAuthFailure = false;
runQuery(query);
if (cleanupCalled && retriesPending == 0) {
cleanUp();
}
}
};
}
auth.invalidateAndRefresh(whenFinished);
}
return false;
}
public int getErrorType() {
return errorType;
}
public String getErrorMessage() {
return errorMessage;
}
// cleanUp must be called when done using this wrapper to close the client.
// Note that the cleanup will be delayed if auth failure retries were
// requested and there is a pending retry.
public void cleanUp() {
if (retriesPending == 0 && !cleanupCalled) {
androidGdataClient.close();
}
cleanupCalled = true;
}
public void setAuthManager(AuthManager newAuth) {
this.auth = newAuth;
}
public void setRetryOnAuthFailure(boolean retry) {
retryOnAuthFailure = retry;
}
}
@@ -1,350 +0,0 @@
/*
* 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.mymaps;
import com.google.android.accounts.Account;
import com.google.android.apps.mymaps.MyMapsGDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.AccountChooser;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.maps.MapFeatureEntry;
import com.google.wireless.gdata.maps.MapsClient;
import com.google.wireless.gdata.parser.GDataParser;
import android.app.Activity;
import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
/**
* Activity which displays the list of "my maps" maps.
*/
public class MyMapsList extends Activity implements ListAdapter {
private static final int MENU_OPEN = 0;
private static final int MENU_SHARE = 2;
private static final int GET_LOGIN = 1;
private LookupThread listLookup;
private Vector<String[]> mapsList;
private Vector<Boolean> publicList;
private Set<DataSetObserver> observerSet;
private Handler loadHandler;
private int contextPosition;
private AuthManager auth;
/**
* Thread which looks up the maps list.
*/
private class LookupThread extends Thread {
private boolean finished = false;
private boolean success = true;
private GDataParser listParser;
@Override
public void run() {
finished = false;
listParser = null;
MyMapsGDataWrapper wrapper = new MyMapsGDataWrapper(MyMapsList.this);
wrapper.setAuthManager(auth);
wrapper.setRetryOnAuthFailure(true);
success = wrapper.runQuery(new QueryFunction() {
public void query(MapsClient client) throws IOException, Exception {
listParser = client.getParserForFeed(
MapFeatureEntry.class, MapsClient.getMapsFeed(),
auth.getAuthToken());
listParser.init();
while (listParser.hasMoreData()) {
MapFeatureEntry entry =
(MapFeatureEntry) listParser.readNextEntry(null);
MyMapsMapMetadata metadata =
MyMapsGDataConverter.getMapMetadataForEntry(entry);
String mapid = MyMapsGDataConverter.getMapidForEntry(entry);
synchronized (mapsList) {
// Search through the maps list to see if it has the mapid and
// remove it if so, so that we can replace it with updated info
for (int i = 0; i < mapsList.size(); ++i) {
if (mapid.equals(mapsList.get(i)[0])) {
mapsList.remove(i);
publicList.remove(i);
--i;
}
}
mapsList.add(new String[] {
mapid, metadata.getTitle(), metadata.getDescription() });
publicList.add(metadata.getSearchable());
}
}
listParser.close();
listParser = null;
success = true;
finished = true;
}
});
if (listParser != null) {
listParser.close();
}
wrapper.cleanUp();
if (success || wrapper.getErrorType() != MyMapsGDataWrapper.ERROR_AUTH) {
finished = true;
}
}
public boolean isFinished() {
return finished;
}
public boolean succeeded() {
return success;
}
}
private final Runnable checkLoaded = new Runnable() {
@Override
public void run() {
if (listLookup.isFinished()) {
findViewById(R.id.loading).setVisibility(View.GONE);
if (!listLookup.succeeded()) {
findViewById(R.id.failed).setVisibility(View.VISIBLE);
}
TextView emptyView = (TextView) findViewById(R.id.mapslist_empty);
ListView list = (ListView) findViewById(R.id.maplist);
list.setEmptyView(emptyView);
} else {
loadHandler.postDelayed(checkLoaded, 500);
}
Iterator<DataSetObserver> iter = observerSet.iterator();
while (iter.hasNext()) {
iter.next().onChanged();
}
}
};
private final OnItemClickListener clickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
Intent result = new Intent();
result.putExtra("mapid", (String) getItem(position));
MyMapsList.this.setResult(RESULT_OK, result);
finish();
}
};
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
menu.add(0, MENU_OPEN, 0, R.string.open_map);
menu.add(0, MENU_SHARE, 0, R.string.share_map);
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
auth = AuthManagerFactory.getAuthManager(this, GET_LOGIN, null, true,
MapsClient.SERVICE);
setContentView(R.layout.list);
ListView list = (ListView) findViewById(R.id.maplist);
list.setOnItemClickListener(clickListener);
list.setOnCreateContextMenuListener(contextMenuListener);
mapsList = new Vector<String[]>();
publicList = new Vector<Boolean>();
listLookup = new LookupThread();
// TODO fix this for non-froyo devices.
if (AuthManagerFactory.useModernAuthManager()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
MyTracks.getInstance().getAccountChooser().chooseAccount(
MyMapsList.this,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
if (account != null) {
// The user did not quit and there was a valid google
// account.
doLogin(account);
}
}
});
}
});
} else {
doLogin(null);
}
observerSet = new HashSet<DataSetObserver>();
list.setAdapter(this);
loadHandler = new Handler();
loadHandler.postDelayed(checkLoaded, 500);
}
private void doLogin(final Account account) {
auth.doLogin(new Runnable() {
public void run() {
listLookup.start();
}
}, account);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == GET_LOGIN) {
auth.authResult(resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case MENU_OPEN:
clickListener.onItemClick(null, null, contextPosition, 0);
return true;
case MENU_SHARE:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getText(R.string.share_map_subject));
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
getText(R.string.share_map_body_format).toString(),
mapsList.get(contextPosition)[1],
MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid="
+ mapsList.get(contextPosition)[0]));
startActivity(Intent.createChooser(shareIntent,
getText(R.string.share_map).toString()));
return true;
}
return false;
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public int getCount() {
return mapsList.size();
}
@Override
public Object getItem(int position) {
return mapsList.get(position)[0];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView =
getLayoutInflater().inflate(R.layout.listitem, parent, false);
}
String[] map = mapsList.get(position);
((TextView) convertView.findViewById(R.id.maplistitem)).setText(map[1]);
((TextView) convertView.findViewById(R.id.maplistdesc)).setText(map[2]);
TextView publicUnlisted =
(TextView) convertView.findViewById(R.id.maplistpublic);
if (publicList.get(position)) {
publicUnlisted.setTextColor(Color.RED);
publicUnlisted.setText(R.string.public_map);
} else {
publicUnlisted.setTextColor(Color.GREEN);
publicUnlisted.setText(R.string.unlisted_map);
}
return convertView;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isEmpty() {
return mapsList.isEmpty();
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
observerSet.add(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
observerSet.remove(observer);
}
}
@@ -1,66 +0,0 @@
/*
* 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.mymaps;
/**
* Metadata about a "my maps" map.
*/
public class MyMapsMapMetadata {
private String title;
private String description;
private String gdataEditUri;
private boolean searchable;
public MyMapsMapMetadata() {
title = "";
description = "";
gdataEditUri = "";
searchable = false;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = new String(title);
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = new String(description);
}
public boolean getSearchable() {
return searchable;
}
public void setSearchable(boolean searchable) {
this.searchable = searchable;
}
public String getGDataEditUri() {
return gdataEditUri;
}
public void setGDataEditUri(String editUri) {
this.gdataEditUri = new String(editUri);
}
}
@@ -85,13 +85,13 @@ public class DialogManager {
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton(activity.getString(R.string.ok), null);
builder.setNeutralButton(activity.getString(R.string.share_map),
builder.setNeutralButton(activity.getString(R.string.share_track),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
activity.shareLinkToMyMap(activity.getSendToMyMapsMapId());
dialog.dismiss();
}
});
public void onClick(DialogInterface dialog, int which) {
activity.shareLinkToFusionTable(activity.sendToTrackId);
dialog.dismiss();
}
});
sendToGoogleResultDialog = builder.create();
return sendToGoogleResultDialog;
case DIALOG_WRITE_PROGRESS:
@@ -119,9 +119,9 @@ public class DialogManager {
sendToGoogleResultDialog.setIcon(success
? android.R.drawable.ic_dialog_info
: android.R.drawable.ic_dialog_alert);
sendToGoogleResultDialog.setMessage(activity.getMapsResultMessage());
sendToGoogleResultDialog.setMessage(activity.getFusionTablesResultMessage());
boolean canShare = activity.getSendToMyMapsMapId() != null;
boolean canShare = activity.getSendToFusionTablesTableId() != null;
View share =
sendToGoogleResultDialog.findViewById(android.R.id.button3);
if (share != null) {
@@ -21,8 +21,6 @@ import static com.google.android.apps.mytracks.DialogManager.DIALOG_SEND_TO_GOOG
import static com.google.android.apps.mytracks.DialogManager.DIALOG_WRITE_PROGRESS;
import com.google.android.accounts.Account;
import com.google.android.apps.mymaps.MyMapsConstants;
import com.google.android.apps.mymaps.MyMapsList;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
@@ -30,11 +28,11 @@ import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.GpxImporter;
import com.google.android.apps.mytracks.io.SendToDocs;
import com.google.android.apps.mytracks.io.SendToMyMaps;
import com.google.android.apps.mytracks.io.SendToFusionTables;
import com.google.android.apps.mytracks.io.TempFileCleaner;
import com.google.android.apps.mytracks.io.TrackWriter;
import com.google.android.apps.mytracks.io.TrackWriterFactory;
import com.google.android.apps.mytracks.io.SendToMyMaps.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
@@ -93,7 +91,6 @@ import org.xml.sax.SAXException;
*/
public class MyTracks extends TabActivity implements OnTouchListener,
OnSharedPreferenceChangeListener, ProgressIndicator {
/**
* Singleton instance
*/
@@ -124,10 +121,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Used by SendToGoogleResultDialog.
*/
public boolean sendToMyMapsSuccess = false;
public long sendToTrackId = -1;
public boolean sendToFusionTablesSuccess = false;
public boolean sendToDocsSuccess = false;
public String sendToMyMapsMapId;
public String sendToMyMapsMessage = "";
public String sendToFusionTablesTableId;
public String sendToFusionTablesMessage = "";
public String sendToDocsMessage = "";
/**
@@ -148,9 +146,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
*/
private long selectedTrackId = -1;
/**
* Does the user want to share the current track.
*/
/**
* Does the user want to share the current track.
*/
private boolean shareRequested = false;
/**
@@ -451,13 +449,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
case MyTracksConstants.GET_MAP: {
if (resultCode == RESULT_OK) {
results.putExtra("trackid", selectedTrackId);
if (results.hasExtra("mapid")) {
sendToMyMapsMapId = results.getStringExtra("mapid");
if (results.hasExtra("tableid")) {
sendToFusionTablesTableId = results.getStringExtra("tableid");
}
setProgressMessage(
R.string.progress_message_authenticating_mymaps);
R.string.progress_message_authenticating_fusiontables);
authenticate(results, MyTracksConstants.SEND_TO_GOOGLE,
MyMapsConstants.MAPSHOP_SERVICE);
SendToFusionTables.SERVICE_ID);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
@@ -517,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);
}
@@ -534,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);
}
@@ -546,8 +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("wise"),
authMap.get("writely"), trackId);
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
sendToTrackId = trackId;
Runnable onCompletion = new Runnable() {
public void run() {
setProgressValue(100);
@@ -556,7 +558,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
public void run() {
sendToDocsMessage = sender.getStatusMessage();
sendToDocsSuccess = sender.wasSuccess();
handleMapsFinish();
handleFusionTablesFinish(trackId);
}
});
}
@@ -569,61 +571,55 @@ public class MyTracks extends TabActivity implements OnTouchListener,
break;
}
case MyTracksConstants.SEND_TO_GOOGLE_DIALOG: {
shareRequested = false;
shareRequested = false;
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
break;
}
case MyTracksConstants.SEND_TO_GOOGLE: {
if (results != null && resultCode == RESULT_OK) {
final String mapId;
final long trackId;
if (results.hasExtra("mapid")) {
mapId = results.getStringExtra("mapid");
} else {
mapId = "new";
}
if (results.hasExtra("trackid")) {
trackId = results.getLongExtra("trackid", -1);
} else {
trackId = selectedTrackId;
}
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
@Override
public void onSendCompleted(String mapId, boolean success,
public void onSendCompleted(String tableId, boolean success,
int statusMessage) {
sendToMyMapsMessage = getString(statusMessage);
sendToMyMapsSuccess = success;
if (sendToMyMapsSuccess) {
sendToMyMapsMapId = mapId;
// Update the map id for this track:
sendToFusionTablesMessage = getString(statusMessage);
sendToFusionTablesSuccess = success;
if (sendToFusionTablesSuccess) {
sendToFusionTablesTableId = tableId;
// Update the table id for this track:
try {
Track track = providerUtils.getTrack(trackId);
track.setMapId(mapId);
track.setTableId(tableId);
providerUtils.updateTrack(track);
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(MyTracksConstants.TAG, "Updating map id failed.", e);
Log.w(MyTracksConstants.TAG, "Updating table id failed.", e);
}
}
if (dialogManager.getSendToGoogleDialog().getSendToDocs()) {
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCS,
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST,
RESULT_OK, new Intent());
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
runOnUiThread(new Runnable() {
public void run() {
handleMapsFinish();
handleFusionTablesFinish(trackId);
}
});
}
}
};
final SendToMyMaps sender = new SendToMyMaps(this, mapId, auth,
sendToTrackId = trackId;
final SendToFusionTables sender = new SendToFusionTables(this, auth,
trackId, this/*progressIndicator*/, onCompletion);
HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(sender);
@@ -654,8 +650,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
case MyTracksConstants.SHARE_LINK: {
Track selectedTrack = providerUtils.getTrack(selectedTrackId);
if (selectedTrack != null) {
if (selectedTrack.getMapId().length() > 0) {
shareLinkToMyMap(selectedTrack.getMapId());
if (selectedTrack.getTableId().length() > 0) {
shareLinkToFusionTable(selectedTrackId);
} else {
shareRequested = true;
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
@@ -724,11 +720,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Resets status information for sending to MyMaps/Docs.
*/
public void resetSendToGoogleStatus() {
sendToMyMapsMessage = "";
sendToMyMapsSuccess = true;
sendToFusionTablesMessage = "";
sendToFusionTablesSuccess = true;
sendToDocsMessage = "";
sendToDocsSuccess = true;
sendToMyMapsMapId = null;
sendToFusionTablesTableId = null;
}
private void importGpxFile(final String fileName) {
@@ -796,10 +792,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
/**
* Shares a link to a My Map via external app (email, gmail, ...)
* Shares a link to a fusion table via external app (email, gmail, ...)
* A chooser with apps that support text/plain will be shown to the user.
*/
public void shareLinkToMyMap(String mapId) {
public void shareLinkToFusionTable(long trackId) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
@@ -811,7 +807,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
getString(R.string.share_url_only_key), false);
}
String url = MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid=" + mapId;
Track track = providerUtils.getTrack(trackId);
String url = SendToFusionTables.getMapVisualizationUrl(track);
String msg = shareUrlOnly ? url : String.format(
getResources().getText(R.string.share_map_body_format).toString(), url);
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
@@ -930,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
@@ -1040,19 +1037,13 @@ public class MyTracks extends TabActivity implements OnTouchListener,
setProgressValue(0);
clearProgressMessage();
dialogManager.showDialogSafely(DIALOG_PROGRESS);
if (sendToGoogleDialog.getSendToMyMaps()) {
if (!sendToGoogleDialog.getCreateNewMap()) {
Intent listIntent = new Intent(this, MyMapsList.class);
startActivityForResult(listIntent, MyTracksConstants.GET_MAP);
} else {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_mymaps);
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
MyMapsConstants.MAPSHOP_SERVICE);
}
if (sendToGoogleDialog.getSendToFusionTables()) {
setProgressValue(0);
setProgressMessage(R.string.progress_message_authenticating_fusiontables);
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());
}
}
@@ -1167,25 +1158,24 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
/**
* Notifies that uploading to maps is finished.
* Notifies that uploading to fusion tables is finished.
*/
private void handleMapsFinish() {
if (shareRequested && sendToMyMapsSuccess) {
private void handleFusionTablesFinish(long trackId) {
if (shareRequested && sendToFusionTablesSuccess) {
// Just share
Toast.makeText(this, getMapsResultMessage(), Toast.LENGTH_LONG).show();
shareLinkToMyMap(sendToMyMapsMapId);
Toast.makeText(this, getFusionTablesResultMessage(), Toast.LENGTH_LONG).show();
shareLinkToFusionTable(trackId);
} else {
dialogManager.showDialogSafely(
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
dialogManager.showDialogSafely(DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
}
}
public String getMapsResultMessage() {
public String getFusionTablesResultMessage() {
StringBuilder message = new StringBuilder();
SendToGoogleDialog sendToGoogleDialog =
dialogManager.getSendToGoogleDialog();
if (sendToGoogleDialog.getSendToMyMaps()) {
message.append(sendToMyMapsMessage);
if (sendToGoogleDialog.getSendToFusionTables()) {
message.append(sendToFusionTablesMessage);
}
if (sendToGoogleDialog.getSendToDocs()) {
if (message.length() > 0) {
@@ -1193,10 +1183,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
message.append(sendToDocsMessage);
}
if (sendToMyMapsSuccess && sendToDocsSuccess) {
message.append(' ');
message.append(getString(R.string.status_mymap_info));
}
return message.toString();
}
@@ -1216,12 +1202,12 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return dialogManager;
}
public String getSendToMyMapsMapId() {
return sendToMyMapsMapId;
public String getSendToFusionTablesTableId() {
return sendToFusionTablesTableId;
}
public boolean getSendToGoogleSuccess() {
return sendToMyMapsSuccess && sendToDocsSuccess;
return sendToFusionTablesSuccess && sendToDocsSuccess;
}
// @VisibleForTesting
@@ -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.
*/
@@ -15,7 +15,6 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mymaps.GeoRect;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
@@ -24,6 +23,7 @@ import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
@@ -789,7 +789,7 @@ public class MyTracksMap extends MapActivity
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
R.string.tracklist_share_track);
share.add(0, MyTracksConstants.MENU_SHARE_LINK, 0,
R.string.tracklist_share_link);
R.string.tracklist_share_linkfusion);
share.add(0, MyTracksConstants.MENU_SHARE_GPX_FILE, 0,
R.string.tracklist_share_gpx_file);
share.add(0, MyTracksConstants.MENU_SHARE_KML_FILE, 0,
@@ -29,7 +29,6 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.RadioGroup;
/**
* A dialog where the user can choose where to send the tracks to, i.e.
@@ -39,10 +38,7 @@ import android.widget.RadioGroup;
*/
public class SendToGoogleDialog extends Dialog {
private RadioGroup groupMyMaps;
private RadioButton createNewMapRadioButton;
private RadioButton pickMapRadioButton;
private CheckBox sendToMyMapsCheckBox;
private CheckBox sendToFusionTablesCheckBox;
private CheckBox sendToDocsCheckBox;
private RadioButton sendStatsRadioButton;
private RadioButton sendStatsAndPointsRadioButton;
@@ -76,26 +72,19 @@ public class SendToGoogleDialog extends Dialog {
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean checked) {
sendButton.setEnabled(
sendToMyMapsCheckBox.isChecked() ||
sendToFusionTablesCheckBox.isChecked() ||
sendToDocsCheckBox.isChecked());
groupMyMaps.setVisibility(
sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
//groupDocs.setVisibility(
// sendToDocsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
}
};
sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
sendToMyMapsCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToFusionTablesCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
createNewMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
pickMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
sendStatsRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_send_stats);
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
@@ -104,18 +93,12 @@ public class SendToGoogleDialog extends Dialog {
SharedPreferences prefs =
getContext().getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
sendToMyMapsCheckBox.setChecked(
sendToFusionTablesCheckBox.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_to_my_maps_key), true));
sendToDocsCheckBox.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_to_docs_key), true));
pickMapRadioButton.setChecked(
prefs.getBoolean(
getContext().getString(R.string.pick_existing_map_key), false));
createNewMapRadioButton.setChecked(
!prefs.getBoolean(
getContext().getString(R.string.pick_existing_map_key), false));
sendStatsAndPointsRadioButton.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_stats_and_points_key),
@@ -136,13 +119,10 @@ public class SendToGoogleDialog extends Dialog {
if (editor != null) {
editor.putBoolean(
getContext().getString(R.string.send_to_my_maps_key),
sendToMyMapsCheckBox.isChecked());
sendToFusionTablesCheckBox.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_to_docs_key),
sendToDocsCheckBox.isChecked());
editor.putBoolean(
getContext().getString(R.string.pick_existing_map_key),
pickMapRadioButton.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_stats_and_points_key),
sendStatsAndPointsRadioButton.isChecked());
@@ -152,18 +132,14 @@ public class SendToGoogleDialog extends Dialog {
super.onStop();
}
public boolean getSendToMyMaps() {
return sendToMyMapsCheckBox.isChecked();
public boolean getSendToFusionTables() {
return sendToFusionTablesCheckBox.isChecked();
}
public boolean getSendToDocs() {
return sendToDocsCheckBox.isChecked();
}
public boolean getCreateNewMap() {
return createNewMapRadioButton.isChecked();
}
public boolean getSendStatsAndPoints() {
return sendStatsAndPointsRadioButton.isChecked();
}
@@ -269,7 +269,7 @@ public class StatsActivity extends Activity
super.onCreateOptionsMenu(menu);
MenuItem currentSegment = menu.add(0,
MyTracksConstants.MENU_CURRENT_SEGMENT, 0, R.string.current_segment);
currentSegment.setIcon(R.drawable.menu_by_distance);
currentSegment.setIcon(R.drawable.ic_menu_lastsegment);
return true;
}
@@ -15,16 +15,13 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -84,16 +81,7 @@ public class WelcomeActivity extends Activity {
builder.setIcon(R.drawable.arrow_icon);
AlertDialog dialog = builder.create();
dialog.show();
// Get the version from the manifest.
try {
PackageInfo pi =
getPackageManager().getPackageInfo("com.google.android.maps.mytracks",
PackageManager.GET_META_DATA);
((TextView) dialog.findViewById(R.id.about_version_register)).
setText(pi.versionName);
} catch (NameNotFoundException e) {
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
}
((TextView) dialog.findViewById(R.id.about_version_register)).
setText(MyTracksUtils.getMyTracksVersion(this));
}
}
@@ -99,7 +99,8 @@ public class MyTracksProvider extends ContentProvider {
+ TracksColumns.ELEVATIONGAIN + " FLOAT, "
+ TracksColumns.MINGRADE + " FLOAT, "
+ TracksColumns.MAXGRADE + " FLOAT, "
+ TracksColumns.MAPID + " STRING);");
+ TracksColumns.MAPID + " STRING, "
+ TracksColumns.TABLEID + " STRING);");
db.execSQL("CREATE TABLE " + WAYPOINTS_TABLE + " ("
+ WaypointsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ WaypointsColumns.NAME + " STRING, "
@@ -146,13 +147,15 @@ public class MyTracksProvider extends ContentProvider {
} else {
// 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.");
+ newVersion + ", adding sensor and tableid columns.");
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
db.execSQL("ALTER TABLE " + TRACKS_TABLE
+ " ADD " + TracksColumns.TABLEID + " STRING");
}
}
}
@@ -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\"");
@@ -15,32 +15,21 @@
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mymaps.MyMapsConstants;
import com.google.android.apps.mytracks.MyTracks;
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;
@@ -48,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.
@@ -65,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;
@@ -95,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(
@@ -196,22 +159,24 @@ public class SendToDocs {
* @param track the track
*/
private boolean uploadToDocs(Track track) {
GDataWrapper wiseWrapper = new GDataWrapper();
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 writelyWrapper = new GDataWrapper();
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("")) {
@@ -220,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...).
@@ -237,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,
@@ -265,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 {
@@ -276,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;
@@ -288,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);
@@ -307,223 +305,4 @@ public class SendToDocs {
}
return true;
}
private boolean getSpreadsheetId(GDataWrapper wrapper, final String title) {
spreadSheetId = null;
return wrapper.runQuery(new QueryFunction() {
@Override
public void query(GDataServiceClient client)
throws IOException, ParseException, HttpException {
GDataParser listParser;
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;
}
}
}
});
}
private boolean getWorkSheetId(GDataWrapper wrapper) {
workSheetId = null;
return wrapper.runQuery(new QueryFunction() {
@Override
public void query(GDataServiceClient client)
throws AuthenticationException, IOException, ParseException {
String uri = String.format(DOCS_WORKSHEETS_URL_FORMAT, spreadSheetId);
GDataParser 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);
}
});
}
/**
* Creates a new MyTracks spreadsheet with the given name.
*/
private boolean createSpreadSheet(GDataWrapper writelyWrapper,
final String name) {
spreadSheetId = null;
return writelyWrapper.runQuery(new QueryFunction() {
@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", MyMapsConstants.MAPSHOP_BASE_URL + "?msa=0&msid="
+ track.getMapId(), 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);
}
}
@@ -0,0 +1,658 @@
/*
* 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;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.ProgressIndicator;
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.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.googleapis.GoogleTransport;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.util.Strings;
import android.app.Activity;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* A helper class used to transmit tracks to Google Fusion Tables.
* A new instance should be used for each upload.
*
* @author Leif Hendrik Wilden
*/
public class SendToFusionTables implements Runnable {
/**
* Listener invoked when sending to fusion tables completes.
*/
public interface OnSendCompletedListener {
void onSendCompleted(String tableId, boolean success, int statusMessage);
}
/** The GData service id for Fusion Tables. */
public static final String SERVICE_ID = "fusiontables";
/** The path for viewing a map visualization of a table. */
private static final String FUSIONTABLES_MAP =
"http://www.google.com/fusiontables/embedviz?" +
"viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&" +
"lat=%f&lng=%f&z=%d&t=1&l=col2";
/** Standard base feed url for Fusion Tables. */
private static final String FUSIONTABLES_BASE_FEED_URL =
"http://www.google.com/fusiontables/api/query";
private static final int MAX_POINTS_PER_UPLOAD = 2048;
private static final String GDATA_VERSION = "2";
private final Activity context;
private final AuthManager auth;
private final long trackId;
private final ProgressIndicator progressIndicator;
private final OnSendCompletedListener onCompletion;
private final StringUtils stringUtils;
private final MyTracksProviderUtils providerUtils;
// Progress status
private int totalLocationsRead;
private int totalLocationsPrepared;
private int totalLocationsUploaded;
private int totalLocations;
private int totalSegmentsUploaded;
private HttpTransport transport;
private String tableId;
private static String MARKER_TYPE_START = "large_green";
private static String MARKER_TYPE_END = "large_red";
private static String MARKER_TYPE_WAYPOINT = "large_yellow";
public SendToFusionTables(Activity context, AuthManager auth,
long trackId, ProgressIndicator progressIndicator,
OnSendCompletedListener onCompletion) {
this.context = context;
this.auth = auth;
this.trackId = trackId;
this.progressIndicator = progressIndicator;
this.onCompletion = onCompletion;
this.stringUtils = new StringUtils(context);
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
transport = GoogleTransport.create();
GoogleHeaders headers = (GoogleHeaders) transport.defaultHeaders;
headers.setApplicationName("Google-MyTracks-" + MyTracksUtils.getMyTracksVersion(context));
headers.gdataVersion = GDATA_VERSION;
}
@Override
public void run() {
Log.d(MyTracksConstants.TAG, "Sending to Fusion tables: trackId = " + trackId);
doUpload();
}
public static String getMapVisualizationUrl(Track track) {
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
TripStatistics stats = track.getStatistics();
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
int z = 15;
return String.format(FUSIONTABLES_MAP, track.getTableId(), latE6 / 1.E6, lonE6 / 1.E6, z);
}
private void doUpload() {
((GoogleHeaders) transport.defaultHeaders).setGoogleLogin(auth.getAuthToken());
int statusMessageId = R.string.error_sending_to_fusiontables;
boolean success = true;
try {
progressIndicator.setProgressValue(1);
progressIndicator.setProgressMessage(R.string.progress_message_reading_track);
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
String originalDescription = track.getDescription();
// Create a new table:
progressIndicator.setProgressMessage(R.string.progress_message_creating_fusiontable);
success = createNewTable(track);
if (success) {
success = makeTableUnlisted(tableId);
}
// Upload all of the segments of the track plus start/end markers
if (success) {
success = uploadAllTrackPoints(track, originalDescription);
}
// Upload all the waypoints.
if (success) {
success = uploadWaypoints(track);
statusMessageId = R.string.status_new_fusiontable_has_been_created;
}
Log.d(MyTracksConstants.TAG, "SendToFusionTables: Done: " + success);
progressIndicator.setProgressValue(100);
} finally {
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
context.runOnUiThread(new Runnable() {
public void run() {
if (onCompletion != null) {
onCompletion.onSendCompleted(
tableId, finalSuccess, finalStatusMessageId);
}
}
});
}
}
/**
* Creates a new table.
* If successful sets {@link #tableId}.
*
* @return true in case of success.
*/
private boolean createNewTable(Track track) {
Log.d(MyTracksConstants.TAG, "Creating a new fusion table.");
String query = "CREATE TABLE '" + track.getName() +
"' (name:STRING,description:STRING,geometry:LOCATION,marker:STRING)";
return runUpdate(query);
}
private boolean makeTableUnlisted(String tableId) {
Log.d(MyTracksConstants.TAG, "Setting visibility to unlisted.");
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = unlisted";
return runUpdate(query);
}
/**
* Formats given values SQL style. Escapes single quotes with a backslash.
*
* @param values the values to format
* @return the values formatted as: ('value1','value2',...,'value_n').
*/
private static String values(String... values) {
StringBuilder builder = new StringBuilder("(");
for (int i = 0; i < values.length; i++) {
if (i > 0) {
builder.append(',');
}
builder.append('\'');
builder.append(values[i].replaceAll("'", "''"));
builder.append('\'');
}
builder.append(')');
return builder.toString();
}
/**
* Creates a new row representing a marker.
*
* @param name the marker name
* @param description the marker description
* @param the marker location
* @return true in case of success.
*/
private boolean createNewPoint(String name, String description, Location location,
String marker) {
Log.d(MyTracksConstants.TAG, "Creating a new row with a point.");
String query = "INSERT INTO " + tableId + " (name,description,geometry,marker) VALUES "
+ values(name, description, getKmlPoint(location), marker);
return runUpdate(query);
}
/**
* Creates a new row representing a line segment.
*
* @param track the track/segment to draw
* @return true in case of success.
*/
private boolean createNewLineString(Track track) {
Log.d(MyTracksConstants.TAG, "Creating a new row with a point.");
String query = "INSERT INTO " + tableId
+ " (name,description,geometry) VALUES "
+ values(track.getName(), track.getDescription(), getKmlLineString(track));
return runUpdate(query);
}
private boolean uploadAllTrackPoints(final Track track, String originalDescription) {
SharedPreferences preferences = context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
boolean metricUnits = true;
if (preferences != null) {
metricUnits = preferences.getBoolean(context.getString(R.string.metric_units_key), true);
}
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
try {
if (!locationsCursor.moveToFirst()) {
Log.w(MyTracksConstants.TAG, "Unable to get any points to upload");
return false;
}
totalLocationsRead = 0;
totalLocationsPrepared = 0;
totalLocationsUploaded = 0;
totalLocations = locationsCursor.getCount();
totalSegmentsUploaded = 0;
// Limit the number of elevation readings. Ideally we would want around 250.
int elevationSamplingFrequency =
Math.max(1, (int) (totalLocations / 250.0));
Log.d(MyTracksConstants.TAG,
"Using elevation sampling factor: " + elevationSamplingFrequency
+ " on " + totalLocations);
double totalDistance = 0;
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
DoubleBuffer elevationBuffer = new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
progressIndicator.setProgressMessage(R.string.progress_message_reading_track);
Location lastLocation = null;
do {
if (totalLocationsRead % 100 == 0) {
updateProgress();
}
Location loc = providerUtils.createLocation(locationsCursor);
locations.add(loc);
if (totalLocationsRead == 0) {
// Put a marker at the first point of the first valid segment:
String name = track.getName() + " " + context.getString(R.string.start);
createNewPoint(name, "", loc, MARKER_TYPE_START);
}
// Add to the elevation profile.
if (loc != null && MyTracksUtils.isValidLocation(loc)) {
// All points go into the smoothing buffer...
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
: loc.getAltitude() * UnitConversions.M_TO_FT);
if (lastLocation != null) {
double dist = lastLocation.distanceTo(loc);
totalDistance += dist;
}
// ...but only a few points are really used to keep the url short.
if (totalLocationsRead % elevationSamplingFrequency == 0) {
distances.add(totalDistance);
elevations.add(elevationBuffer.getAverage());
}
}
// If the location was not valid, it's a segment split, so make sure the
// distance between the previous segment and the new one is not accounted
// for in the next iteration.
lastLocation = loc;
// Every now and then, upload the accumulated points
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD == MAX_POINTS_PER_UPLOAD - 1) {
if (!prepareAndUploadPoints(track, locations)) {
return false;
}
}
totalLocationsRead++;
} while (locationsCursor.moveToNext());
// Do a final upload with what's left
if (!prepareAndUploadPoints(track, locations)) {
return false;
}
// Put an end marker at the last point of the last valid segment:
if (lastLocation != null) {
track.setDescription("<p>" + originalDescription + "</p><p>"
+ stringUtils.generateTrackDescription(track, distances, elevations)
+ "</p>");
String name = track.getName() + " " + context.getString(R.string.end);
return createNewPoint(name, track.getDescription(), lastLocation, MARKER_TYPE_END);
}
return true;
} finally {
locationsCursor.close();
}
}
/**
* Appends the given location to the string in the format:
* longitude,latitude[,altitude]
*
* @param location the location to be added
* @param builder the string builder to use
*/
private void appendCoordinate(Location location, StringBuilder builder) {
builder
.append(location.getLongitude())
.append(",")
.append(location.getLatitude());
if (location.hasAltitude()) {
builder.append(",");
builder.append(location.getAltitude());
}
}
/**
* Gets a KML Point tag for the given location.
*
* @param location The location.
* @return the kml.
*/
private String getKmlPoint(Location location) {
StringBuilder builder = new StringBuilder("<Point><coordinates>");
appendCoordinate(location, builder);
builder.append("</coordinates></Point>");
return builder.toString();
}
/**
* Returns a KML Point tag for the given location.
*
* @param location The location.
* @return the kml.
*/
private String getKmlLineString(Track track) {
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
for (Location location : track.getLocations()) {
appendCoordinate(location, builder);
builder.append(' ');
}
builder.append("</coordinates></LineString>");
return builder.toString();
}
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
progressIndicator.setProgressMessage(R.string.progress_message_preparing_track);
updateProgress();
int numLocations = locations.size();
if (numLocations < 2) {
Log.d(MyTracksConstants.TAG, "Not preparing/uploading too few points");
totalLocationsUploaded += numLocations;
return true;
}
// Prepare/pre-process the points
ArrayList<Track> splitTracks = prepareLocations(track, locations);
// Start uploading them
progressIndicator.setProgressMessage(R.string.progress_message_sending_fusiontables);
for (Track splitTrack : splitTracks) {
if (totalSegmentsUploaded > 1) {
splitTrack.setName(splitTrack.getName() + " "
+ String.format(
context.getString(R.string.part), totalSegmentsUploaded));
}
totalSegmentsUploaded++;
Log.d(MyTracksConstants.TAG,
"SendToFusionTables: Prepared feature for upload w/ "
+ splitTrack.getLocations().size() + " points.");
// Transmit tracks via GData feed:
// -------------------------------
Log.d(MyTracksConstants.TAG,
"SendToFusionTables: Uploading to table " + tableId + " w/ auth " + auth);
if (!uploadTrackPoints(splitTrack)) {
Log.e(MyTracksConstants.TAG, "Uploading failed");
return false;
}
}
locations.clear();
totalLocationsUploaded += numLocations;
updateProgress();
return true;
}
/**
* Prepares a buffer of locations for transmission to google fusion tables.
*
* @param track the original track with meta data
* @param buffer a buffer of locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
private ArrayList<Track> prepareLocations(
Track track, Iterable<Location> locations) {
ArrayList<Track> splitTracks = new ArrayList<Track>();
// Create segments from each full track:
Track segment = new Track();
TripStatistics segmentStats = segment.getStatistics();
TripStatistics trackStats = track.getStatistics();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
segmentStats.setStartTime(trackStats.getStartTime());
segmentStats.setStopTime(trackStats.getStopTime());
boolean startNewTrackSegment = false;
for (Location loc : locations) {
if (totalLocationsPrepared % 100 == 0) {
updateProgress();
}
if (loc.getLatitude() > 90) {
startNewTrackSegment = true;
}
if (startNewTrackSegment) {
// Close up the last segment.
prepareTrackSegment(segment, splitTracks);
Log.d(MyTracksConstants.TAG,
"MyTracksSendToFusionTables: Starting new track segment...");
startNewTrackSegment = false;
segment = new Track();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
}
if (loc.getLatitude() <= 90) {
segment.addLocation(loc);
if (segmentStats.getStartTime() < 0) {
segmentStats.setStartTime(loc.getTime());
}
}
totalLocationsPrepared++;
}
prepareTrackSegment(segment, splitTracks);
return splitTracks;
}
/**
* Prepares a track segment for sending to google fusion tables.
* The main steps are:
* - correcting end time
* - decimating locations
* - splitting into smaller tracks.
*
* The final track pieces will be put in the array list splitTracks.
*
* @param segment the original segment of the track
* @param splitTracks an array of smaller track segments
*/
private void prepareTrackSegment(
Track segment, ArrayList<Track> splitTracks) {
TripStatistics segmentStats = segment.getStatistics();
if (segmentStats.getStopTime() < 0
&& segment.getLocations().size() > 0) {
segmentStats.setStopTime(segment.getLocations().size() - 1);
}
/*
* Decimate to 2 meter precision. Fusion tables doesn't like too many
* points:
*/
MyTracksUtils.decimate(segment, 2.0);
/* If the track still has > 2500 points, split it in pieces: */
final int maxPoints = 2500;
if (segment.getLocations().size() > maxPoints) {
splitTracks.addAll(MyTracksUtils.split(segment, maxPoints));
} else if (segment.getLocations().size() >= 2) {
splitTracks.add(segment);
}
}
private boolean uploadTrackPoints(Track splitTrack) {
int numLocations = splitTrack.getLocations().size();
if (numLocations < 2) {
// Need at least two points for a polyline:
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
return true;
}
return createNewLineString(splitTrack);
}
/**
* Uploads all of the waypoints associated with this track to a table.
*
* @param track The track to upload waypoints for.
*
* @return True on success.
*/
private boolean uploadWaypoints(final Track track) {
// TODO: Stream through the waypoints in chunks.
// I am leaving the number of waypoints very high which should not be a
// problem because we don't try to load them into objects all at the
// same time.
boolean success = true;
Cursor c = null;
c = providerUtils.getWaypointsCursor(
track.getId(), 0,
MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS);
if (c != null) {
if (c.moveToFirst()) {
// This will skip the 1st waypoint (it carries the stats for the
// last segment).
while (c.moveToNext()) {
Waypoint wpt = providerUtils.createWaypoint(c);
Log.d(MyTracksConstants.TAG, "SendToFusionTables: Creating waypoint.");
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
MARKER_TYPE_WAYPOINT);
if (!success) {
break;
}
}
}
}
if (!success) {
Log.w(MyTracksConstants.TAG, "SendToFusionTables: upload waypoints failed.");
}
return success;
}
/**
* Sets the current upload progress.
*/
private void updateProgress() {
// The percent of the total that represents the completed part of this
// segment.
int totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
/ (totalLocations * 3);
totalPercentage = Math.min(99, totalPercentage);
progressIndicator.setProgressValue(totalPercentage);
}
/**
* Runs an update query. Handles authentication.
*
* @param query The given SQL like query
* @return true in case of success
*/
private boolean runUpdate(final String query) {
GDataWrapper<HttpTransport> wrapper = new GDataWrapper<HttpTransport>();
wrapper.setAuthManager(auth);
wrapper.setRetryOnAuthFailure(true);
wrapper.setClient(transport);
Log.d(MyTracksConstants.TAG, "GData connection prepared: " + this.auth);
wrapper.runQuery(new QueryFunction<HttpTransport>() {
@Override
public void query(HttpTransport client)
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
GDataWrapper.AuthenticationException {
HttpRequest request = transport.buildPostRequest();
request.headers.contentType = "application/x-www-form-urlencoded";
GenericUrl url = new GenericUrl(FUSIONTABLES_BASE_FEED_URL);
request.url = url;
InputStreamContent isc = new InputStreamContent();
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
isc.inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
request.content = isc;
Log.d(MyTracksConstants.TAG, "Running update query " + url.toString() + ": " + sql);
try {
HttpResponse response = request.execute();
boolean success = response.isSuccessStatusCode;
if (success) {
byte[] result = new byte[1024];
response.getContent().read(result);
String s = Strings.fromBytesUtf8(result);
String[] lines = s.split(Strings.LINE_SEPARATOR);
if (lines[0].equals("tableid")) {
tableId = lines[1];
Log.d(MyTracksConstants.TAG, "tableId = " + tableId);
} else {
Log.w(MyTracksConstants.TAG, "Unrecognized response: " + lines[0]);
}
} else {
Log.d(MyTracksConstants.TAG, "Query failed: " + response.statusMessage + " (" +
response.statusCode + ")");
throw new GDataWrapper.HttpException(response.statusCode, response.statusMessage);
}
} catch (HttpResponseException e) {
if (e.response.statusCode == 401) {
throw new GDataWrapper.AuthenticationException(e);
}
}
}
});
return wrapper.getErrorType() == GDataWrapper.ERROR_NO_ERROR;
}
}
@@ -1,689 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mymaps.MyMapsFeature;
import com.google.android.apps.mymaps.MyMapsGDataConverter;
import com.google.android.apps.mymaps.MyMapsGDataWrapper;
import com.google.android.apps.mymaps.MyMapsGDataWrapper.QueryFunction;
import com.google.android.apps.mymaps.MyMapsMapMetadata;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.ProgressIndicator;
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.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.maps.MapsClient;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.xmlpull.v1.XmlPullParserException;
/**
* A helper class used to transmit tracks to Google MyMaps.
* A new instance should be used for each upload.
*
* @author Leif Hendrik Wilden
*/
public class SendToMyMaps implements Runnable {
public static final String NEW_MAP_ID = "new";
private static final String END_ICON_URL =
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
private static final String START_ICON_URL =
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
private static final int MAX_POINTS_PER_UPLOAD = 2048;
private final Activity context;
private final AuthManager auth;
private final long trackId;
private final ProgressIndicator progressIndicator;
private final OnSendCompletedListener onCompletion;
private final StringUtils stringUtils;
private final MyTracksProviderUtils providerUtils;
private String mapId;
// GData service
private MyMapsGDataWrapper wrapper;
private MyMapsGDataConverter gdataConverter;
// Progress status
private int totalLocationsRead;
private int totalLocationsPrepared;
private int totalLocationsUploaded;
private int totalLocations;
private int totalSegmentsUploaded;
public interface OnSendCompletedListener {
void onSendCompleted(String mapId, boolean success, int statusMessage);
}
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
long trackId, ProgressIndicator progressIndicator,
OnSendCompletedListener onCompletion) {
this.context = context;
this.mapId = mapId;
this.auth = auth;
this.trackId = trackId;
this.progressIndicator = progressIndicator;
this.onCompletion = onCompletion;
this.stringUtils = new StringUtils(context);
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
}
@Override
public void run() {
Log.d(MyTracksConstants.TAG, "Sending to MyMaps: trackId = " + trackId);
doUpload();
}
private void doUpload() {
int statusMessageId = R.string.error_sending_to_mymap;
boolean success = true;
try {
gdataConverter = new MyMapsGDataConverter();
progressIndicator.setProgressValue(1);
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
String originalDescription = track.getDescription();
track.setDescription("<p>" + track.getDescription() + "</p><p>"
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
wrapper = new MyMapsGDataWrapper(context);
wrapper.setAuthManager(auth);
wrapper.setRetryOnAuthFailure(true);
// Create a new map if necessary:
boolean isNewMap = mapId.equals(NEW_MAP_ID);
if (isNewMap) {
success = createNewMap(track, originalDescription);
}
// Upload all of the segments of the track plus start/end markers
if (success) {
success = uploadAllTrackPoints(track, originalDescription);
}
// Put waypoints.
if (success) {
success = uploadWaypoints(track);
if (!success) {
Log.w(MyTracksConstants.TAG,
"SendToMyMaps: upload waypoints failed.");
}
}
if (success) {
statusMessageId = isNewMap
? R.string.status_new_mymap_has_been_created
: R.string.status_tracks_have_been_uploaded;
}
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
progressIndicator.setProgressValue(100);
} catch (XmlPullParserException e) {
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
} finally {
if (wrapper != null) {
wrapper.cleanUp();
}
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
context.runOnUiThread(new Runnable() {
public void run() {
if (onCompletion != null) {
onCompletion.onSendCompleted(
mapId, finalSuccess, finalStatusMessageId);
}
}
});
}
}
/**
* Creates a new map for the given track.
*
* @param track The track that will be uploaded to this map
* @return True on success.
*/
private boolean createNewMap(final Track track, final String description) {
progressIndicator.setProgressMessage(
R.string.progress_message_creating_map);
return wrapper.runQuery(new QueryFunction() {
@Override
public void query(MapsClient client) throws IOException, Exception {
Log.d(MyTracksConstants.TAG, "Creating a new map.");
String mapFeed = MapsClient.getMapsFeed();
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
metaData.setTitle(track.getName());
metaData.setDescription(description + " - "
+ track.getCategory() + " - "
+ context.getString(R.string.new_map_description));
SharedPreferences preferences = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
boolean mapPublic = true;
if (preferences != null) {
mapPublic = preferences.getBoolean(
context.getString(R.string.default_map_public_key), true);
}
metaData.setSearchable(mapPublic);
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
}
});
}
private boolean uploadAllTrackPoints(
final Track track, String originalDescription) {
SharedPreferences preferences = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
boolean metricUnits = true;
if (preferences != null) {
metricUnits =
preferences.getBoolean(context.getString(R.string.metric_units_key),
true);
}
Cursor locationsCursor =
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
try {
if (!locationsCursor.moveToFirst()) {
Log.w(MyTracksConstants.TAG, "Unable to get any points to upload");
return false;
}
totalLocationsRead = 0;
totalLocationsPrepared = 0;
totalLocationsUploaded = 0;
totalLocations = locationsCursor.getCount();
totalSegmentsUploaded = 0;
// Limit the number of elevation readings. Ideally we would want around 250.
int elevationSamplingFrequency =
Math.max(1, (int) (totalLocations / 250.0));
Log.d(MyTracksConstants.TAG,
"Using elevation sampling factor: " + elevationSamplingFrequency
+ " on " + totalLocations);
double totalDistance = 0;
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
DoubleBuffer elevationBuffer =
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
Location lastLocation = null;
do {
if (totalLocationsRead % 100 == 0) {
updateProgress();
}
Location loc = providerUtils.createLocation(locationsCursor);
locations.add(loc);
if (totalLocationsRead == 0) {
// Put a marker at the first point of the first valid segment:
uploadMarker(track, loc, true);
}
// Add to the elevation profile.
if (loc != null && MyTracksUtils.isValidLocation(loc)) {
// All points go into the smoothing buffer...
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
: loc.getAltitude() * UnitConversions.M_TO_FT);
if (lastLocation != null) {
double dist = lastLocation.distanceTo(loc);
totalDistance += dist;
}
// ...but only a few points are really used to keep the url short.
if (totalLocationsRead % elevationSamplingFrequency == 0) {
distances.add(totalDistance);
elevations.add(elevationBuffer.getAverage());
}
}
// If the location was not valid, it's a segment split, so make sure the
// distance between the previous segment and the new one is not accounted
// for in the next iteration.
lastLocation = loc;
// Every now and then, upload the accumulated points
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
== MAX_POINTS_PER_UPLOAD - 1) {
if (!prepareAndUploadPoints(track, locations)) {
return false;
}
}
totalLocationsRead++;
} while (locationsCursor.moveToNext());
// Do a final upload with what's left
if (!prepareAndUploadPoints(track, locations)) {
return false;
}
// Put an end marker at the last point of the last valid segment:
if (lastLocation != null) {
track.setDescription("<p>" + originalDescription + "</p><p>"
+ stringUtils.generateTrackDescription(
track, distances, elevations)
+ "</p>");
return uploadMarker(track, lastLocation, false);
}
return true;
} finally {
locationsCursor.close();
}
}
private boolean uploadMarker(final Track track,
final Location location,
final boolean isStart) {
boolean okay = wrapper.runQuery(new QueryFunction() {
@Override
public void query(MapsClient client)
throws AuthenticationException, IOException, Exception {
String featureFeed = MapsClient.getFeaturesFeed(mapId);
insertMarker(context, client, featureFeed, track, location, isStart);
}
});
return okay;
}
/**
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
* 2nd failure.
*/
private void insertMarker(Context context, MapsClient client,
String featureFeed, Track track, Location loc,
boolean isStart) throws IOException, Exception {
Entry entry = gdataConverter.getEntryForFeature(
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
+ entry.getTitle());
try {
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
} catch (IOException e) {
Log.w(MyTracksConstants.TAG,
"SendToMyMaps: createEntry 1st try failed. Trying again.");
// Retry once (often IOException is thrown on a timeout):
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: createEntry success on 2nd try!");
}
}
/**
* Builds a placemark MyMapsFeature from a track.
*
* @param track the track
* @param isStart true if it's the start of the track, or false for end
* @return a MyMapsFeature
*/
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
Track track, Location loc, boolean isStart) {
MyMapsFeature myMapsFeature = new MyMapsFeature();
myMapsFeature.generateAndroidId();
myMapsFeature.setType(MyMapsFeature.MARKER);
if (isStart) {
myMapsFeature.setIconUrl(START_ICON_URL);
} else {
myMapsFeature.setIconUrl(END_ICON_URL);
}
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
String name = track.getName() + " "
+ (isStart ? context.getString(R.string.start)
: context.getString(R.string.end));
myMapsFeature.setTitle(name);
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
return myMapsFeature;
}
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
progressIndicator.setProgressMessage(
R.string.progress_message_preparing_track);
updateProgress();
int numLocations = locations.size();
if (numLocations < 2) {
Log.d(MyTracksConstants.TAG, "Not preparing/uploading too few points");
totalLocationsUploaded += numLocations;
return true;
}
// Prepare/pre-process the points
ArrayList<Track> splitTracks = prepareLocations(track, locations);
// Start uploading them
progressIndicator.setProgressMessage(
R.string.progress_message_sending_mymaps);
for (Track splitTrack : splitTracks) {
if (totalSegmentsUploaded > 1) {
splitTrack.setName(splitTrack.getName() + " "
+ String.format(
context.getString(R.string.part), totalSegmentsUploaded));
}
totalSegmentsUploaded++;
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: Prepared feature for upload w/ "
+ splitTrack.getLocations().size() + " points.");
// Transmit tracks via GData feed:
// -------------------------------
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
if (!uploadTrackPoints(splitTrack)) {
Log.e(MyTracksConstants.TAG, "Uploading failed");
return false;
}
}
locations.clear();
totalLocationsUploaded += numLocations;
updateProgress();
return true;
}
/**
* Prepares a buffer of locations for transmission to google maps.
*
* @param track the original track with meta data
* @param buffer a buffer of locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
private ArrayList<Track> prepareLocations(
Track track, Iterable<Location> locations) {
ArrayList<Track> splitTracks = new ArrayList<Track>();
// Create segments from each full track:
Track segment = new Track();
TripStatistics segmentStats = segment.getStatistics();
TripStatistics trackStats = track.getStatistics();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
segmentStats.setStartTime(trackStats.getStartTime());
segmentStats.setStopTime(trackStats.getStopTime());
boolean startNewTrackSegment = false;
for (Location loc : locations) {
if (totalLocationsPrepared % 100 == 0) {
updateProgress();
}
if (loc.getLatitude() > 90) {
startNewTrackSegment = true;
}
if (startNewTrackSegment) {
// Close up the last segment.
prepareTrackSegment(segment, splitTracks);
Log.d(MyTracksConstants.TAG,
"MyTracksSendToMyMaps: Starting new track segment...");
startNewTrackSegment = false;
segment = new Track();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
}
if (loc.getLatitude() <= 90) {
segment.addLocation(loc);
if (segmentStats.getStartTime() < 0) {
segmentStats.setStartTime(loc.getTime());
}
}
totalLocationsPrepared++;
}
prepareTrackSegment(segment, splitTracks);
return splitTracks;
}
/**
* Prepares a track segment for sending to google maps.
* The main steps are:
* - correcting end time
* - decimating locations
* - splitting into smaller tracks.
*
* The final track pieces will be put in the array list splitTracks.
*
* @param segment the original segment of the track
* @param splitTracks an array of smaller track segments
*/
private void prepareTrackSegment(
Track segment, ArrayList<Track> splitTracks) {
TripStatistics segmentStats = segment.getStatistics();
if (segmentStats.getStopTime() < 0
&& segment.getLocations().size() > 0) {
segmentStats.setStopTime(segment.getLocations().size() - 1);
}
/*
* Decimate to 2 meter precision. Mapshop doesn't like too many
* points:
*/
MyTracksUtils.decimate(segment, 2.0);
/* It the track still has > 500 points, split it in pieces: */
if (segment.getLocations().size() > 500) {
splitTracks.addAll(MyTracksUtils.split(segment, 500));
} else if (segment.getLocations().size() >= 2) {
splitTracks.add(segment);
}
}
/**
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
*/
private boolean uploadTrackPoints(final Track track) {
return wrapper.runQuery(new QueryFunction() {
@Override
public void query(MapsClient client)
throws AuthenticationException, IOException, Exception {
String featureFeed = MapsClient.getFeaturesFeed(mapId);
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
uploadTrackPoints(track, client, featureFeed);
}
});
}
private boolean uploadTrackPoints(Track splitTrack,
MapsClient client,
String featureFeed)
throws IOException, Exception {
Entry entry = null;
int numLocations = splitTrack.getLocations().size();
if (numLocations < 2) {
// Need at least two points for a polyline:
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
return true;
}
// Put the line:
entry = gdataConverter.getEntryForFeature(
buildMyMapsLineFeature(splitTrack));
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: Creating line " + entry.getTitle());
try {
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
} catch (IOException e) {
Log.w(MyTracksConstants.TAG,
"SendToMyMaps: createEntry 1st try failed. Trying again.");
// Retry once (often IOException is thrown on a timeout):
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: createEntry success on 2nd try!");
}
return true;
}
/**
* Builds a MyMapsFeature from a track.
*
* @param track the track
* @return a MyMapsFeature
*/
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
MyMapsFeature myMapsFeature = new MyMapsFeature();
myMapsFeature.generateAndroidId();
myMapsFeature.setType(MyMapsFeature.LINE);
if (track.getName().length() < 1) {
// Features must have a name (otherwise GData upload may fail):
myMapsFeature.setTitle("-");
} else {
myMapsFeature.setTitle(track.getName());
}
myMapsFeature.setColor(0x80FF0000);
for (Location loc : track.getLocations()) {
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
}
return myMapsFeature;
}
/**
* Uploads all of the waypoints associated with this track to map.
*
* @param track The track to upload waypoints for.
*
* @return True on success.
*/
private boolean uploadWaypoints(final Track track) {
return wrapper.runQuery(new QueryFunction() {
public void query(MapsClient client) {
// TODO: Stream through he waypoints in chunks.
// I am leaving the number of waypoints very high which should not be a
// problem because we don't try to load them into objects all at the
// same time.
Cursor c = null;
c = providerUtils.getWaypointsCursor(
track.getId(), 0,
MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS);
String featureFeed = MapsClient.getFeaturesFeed(mapId);
if (c != null) {
try {
if (c.moveToFirst()) {
// This will skip the 1st waypoint (it carries the stats for the
// last segment).
while (c.moveToNext()) {
Waypoint wpt = providerUtils.createWaypoint(c);
Entry entry = gdataConverter.getEntryForFeature(
buildMyMapsPlacemarkFeature(wpt));
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: Creating waypoint.");
try {
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: createEntry success!");
} catch (IOException e) {
Log.w(MyTracksConstants.TAG,
"SendToMyMaps: createEntry 1st try failed. Retrying.");
// Retry once (often IOException is thrown on a timeout):
client.createEntry(featureFeed, auth.getAuthToken(), entry);
Log.d(MyTracksConstants.TAG,
"SendToMyMaps: createEntry success on 2nd try!");
}
}
}
} catch (ParseException e) {
Log.w(MyTracksConstants.TAG, "ParseException caught.", e);
} catch (HttpException e) {
Log.w(MyTracksConstants.TAG, "HttpException caught.", e);
} catch (IOException e) {
Log.w(MyTracksConstants.TAG, "IOException caught.", e);
} finally {
c.close();
}
}
}
});
}
/**
* Builds a MyMapsFeature from a track.
*
* @param wpt the waypoint
* @return a MyMapsFeature
*/
private static MyMapsFeature buildMyMapsPlacemarkFeature(Waypoint wpt) {
MyMapsFeature myMapsFeature = new MyMapsFeature();
myMapsFeature.generateAndroidId();
myMapsFeature.setType(MyMapsFeature.MARKER);
myMapsFeature.setIconUrl(wpt.getIcon());
myMapsFeature.setDescription(wpt.getDescription());
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(wpt.getLocation()));
if (wpt.getName().length() < 1) {
// Features must have a name (otherwise GData upload may fail):
myMapsFeature.setTitle("-");
} else {
myMapsFeature.setTitle(wpt.getName());
}
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
return myMapsFeature;
}
/**
* Sets the current upload progress.
*/
private void updateProgress() {
// The percent of the total that represents the completed part of this
// segment.
int totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
/ (totalLocations * 3);
totalPercentage = Math.min(99, totalPercentage);
progressIndicator.setProgressValue(totalPercentage);
}
}
@@ -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\" ");
@@ -37,13 +37,15 @@ public class BackupColumns {
TracksColumns.MOVINGTIME, TracksColumns.AVGSPEED, TracksColumns.AVGMOVINGSPEED,
TracksColumns.MAXSPEED, TracksColumns.MINELEVATION, TracksColumns.MAXELEVATION,
TracksColumns.ELEVATIONGAIN, TracksColumns.MINGRADE, TracksColumns.MAXGRADE,
TracksColumns.MINLAT, TracksColumns.MAXLAT, TracksColumns.MINLON, TracksColumns.MAXLON };
TracksColumns.MINLAT, TracksColumns.MAXLAT, TracksColumns.MINLON, TracksColumns.MAXLON,
TracksColumns.MAPID, TracksColumns.TABLEID};
public static final byte[] TRACKS_BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, STRING_TYPE_ID,
STRING_TYPE_ID};
public static final String[] WAYPOINTS_BACKUP_COLUMNS = {
WaypointsColumns._ID, WaypointsColumns.TRACKID, WaypointsColumns.NAME,
@@ -0,0 +1,372 @@
/*
* 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,
GDataWrapper.AuthenticationException {
// 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,
GDataWrapper.AuthenticationException {
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(">");
}
}
@@ -17,11 +17,6 @@ package com.google.android.apps.mytracks.io.gdata;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.wireless.gdata.client.GDataServiceClient;
import com.google.wireless.gdata.client.HttpException;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.ConflictDetectedException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.util.Log;
@@ -39,9 +34,60 @@ import java.util.concurrent.TimeoutException;
* handling. After a query is run, the wrapper can be queried about the error
* that occurred.
*
* @param C the GData service client
* @author Sandor Dornbush
*/
public class GDataWrapper {
public class GDataWrapper<C> {
public static class AuthenticationException extends Exception {
private Exception exception;
private static final long serialVersionUID = 1L;
public AuthenticationException(Exception caught) {
this.exception = caught;
}
public Exception getException() {
return exception;
}
};
public static class ParseException extends Exception {
private Exception exception;
private static final long serialVersionUID = 1L;
public ParseException(Exception caught) {
this.exception = caught;
}
public Exception getException() {
return exception;
}
};
public static class ConflictDetectedException extends Exception {
private Exception exception;
private static final long serialVersionUID = 1L;
public ConflictDetectedException(Exception caught) {
this.exception = caught;
}
public Exception getException() {
return exception;
}
};
public static class HttpException extends Exception {
private static final long serialVersionUID = 1L;
private int statusCode;
private String statusMessage;
public HttpException(int statusCode, String statusMessage) {
super();
this.statusCode = statusCode;
this.statusMessage = statusMessage;
}
public int getStatusCode() {
return statusCode;
}
public String getStatusMessage() {
return statusMessage;
}
};
/**
* A QueryFunction is passed in when executing a query. The query function of
@@ -49,8 +95,8 @@ public class GDataWrapper {
* should execute whatever operations it desires on the client without concern
* for whether the client will throw an error.
*/
public interface QueryFunction {
public abstract void query(GDataServiceClient client)
public interface QueryFunction<C> {
public abstract void query(C client)
throws AuthenticationException, IOException, ParseException,
ConflictDetectedException, HttpException;
}
@@ -92,7 +138,7 @@ public class GDataWrapper {
private String errorMessage;
private int errorType;
private GDataServiceClient gdataServiceClient;
private C gdataServiceClient;
private AuthManager auth;
private boolean retryOnAuthFailure;
@@ -103,7 +149,7 @@ public class GDataWrapper {
retryOnAuthFailure = false;
}
public void setClient(GDataServiceClient gdataServiceClient) {
public void setClient(C gdataServiceClient) {
this.gdataServiceClient = gdataServiceClient;
}
@@ -112,7 +158,7 @@ public class GDataWrapper {
return runCommon(function, null);
}
public boolean runQuery(final QueryFunction query) {
public boolean runQuery(final QueryFunction<C> query) {
return runCommon(null, query);
}
@@ -120,7 +166,7 @@ public class GDataWrapper {
* Runs an arbitrary piece of code.
*/
private boolean runCommon(final AuthenticatedFunction function,
final QueryFunction query) {
final QueryFunction<C> query) {
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
runOne(function, query);
if (errorType == ERROR_NO_ERROR) {
@@ -144,7 +190,7 @@ public class GDataWrapper {
* errorMessage will contain the result/status of the function/query.
*/
private void runOne(final AuthenticatedFunction function,
final QueryFunction query) {
final QueryFunction<C> query) {
try {
if (function != null) {
function.run(this.auth.getAuthToken());
@@ -160,7 +206,7 @@ public class GDataWrapper {
errorMessage = null;
} catch (AuthenticationException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
Log.e(MyTracksConstants.TAG, "AuthenticationException", e);
errorType = ERROR_AUTH;
errorMessage = e.getMessage();
} catch (HttpException e) {
@@ -243,6 +289,10 @@ public class GDataWrapper {
this.auth = auth;
}
public AuthManager getAuthManager() {
return auth;
}
public void setRetryOnAuthFailure(boolean retry) {
retryOnAuthFailure = retry;
}
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mymaps;
package com.google.android.apps.mytracks.util;
import com.google.android.maps.GeoPoint;
@@ -61,4 +61,4 @@ public class GeoRect {
}
return false;
}
}
}
@@ -22,11 +22,11 @@ import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.GeoPoint;
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.os.StrictMode;
import android.util.Log;
import java.util.ArrayList;
@@ -267,12 +267,28 @@ public class MyTracksUtils {
} catch (NameNotFoundException e) {
Log.e(MyTracksConstants.TAG, "Unable to get signatures", e);
}
return false;
}
/**
* This is a utility class w/ only static memebers.
* Get the My Tracks version from the manifest.
*
* @return the version, or an empty string in case of failure.
*/
public static String getMyTracksVersion(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
"com.google.android.maps.mytracks",
PackageManager.GET_META_DATA);
return pi.versionName;
} catch (NameNotFoundException e) {
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
return "";
}
}
/**
* This is a utility class w/ only static members.
*/
protected MyTracksUtils() {
}
@@ -316,7 +316,7 @@ public class StringUtils implements DescriptionGenerator {
+ "<img border=\"0\" src=\"%s\"/>",
// Line 1
context.getString(R.string.my_map_link),
context.getString(R.string.fusiontable_link),
// Line 2
context.getString(R.string.total_distance_label),
@@ -101,6 +101,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.MAPID, track.getMapId());
values.put(TracksColumns.TABLEID, track.getTableId());
values.put(TracksColumns.CATEGORY, track.getCategory());
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
values.put(TracksColumns.STARTID, track.getStartId());
@@ -269,6 +270,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int idxDescription =
cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
int idxMapId = cursor.getColumnIndexOrThrow(TracksColumns.MAPID);
int idxTableId = cursor.getColumnIndexOrThrow(TracksColumns.TABLEID);
int idxCategory = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int idxStartId = cursor.getColumnIndexOrThrow(TracksColumns.STARTID);
int idxStartTime = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
@@ -308,6 +310,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxMapId)) {
track.setMapId(cursor.getString(idxMapId));
}
if (!cursor.isNull(idxTableId)) {
track.setTableId(cursor.getString(idxTableId));
}
if (!cursor.isNull(idxCategory)) {
track.setCategory(cursor.getString(idxCategory));
}
@@ -56,6 +56,7 @@ public class Track implements Parcelable {
track.locations.add(loc);
}
track.tableId = source.readString();
return track;
}
@@ -81,6 +82,7 @@ public class Track implements Parcelable {
private String name = "";
private String description = "";
private String mapId = "";
private String tableId = "";
private long startId = -1;
private long stopId = -1;
private String category = "";
@@ -104,6 +106,8 @@ public class Track implements Parcelable {
for (int i = 0; i < numberOfPoints; ++i) {
dest.writeParcelable(locations.get(i), 0);
}
dest.writeString(tableId);
}
// Getters and setters:
@@ -161,6 +165,14 @@ public class Track implements Parcelable {
this.mapId = mapId;
}
public String getTableId() {
return tableId;
}
public void setTableId(String tableId) {
this.tableId = tableId;
}
public String getCategory() {
return category;
}
@@ -59,4 +59,5 @@ public interface TracksColumns extends BaseColumns {
public static final String MINLON = "minlon";
public static final String MAXLON = "maxlon";
public static final String MAPID = "mapid";
public static final String TABLEID = "tableid";
}
+2 -1
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry combineaccessrules="false" kind="src" path="/MyTracks"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
@@ -10,5 +9,7 @@
<attribute name="optional" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/MyTracks"/>
<classpathentry kind="lib" path="/MyTracks/libs/google-common.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -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());
}
}