diff --git a/MyTracks/.classpath b/MyTracks/.classpath index ecdfd68ee..0734acf85 100644 --- a/MyTracks/.classpath +++ b/MyTracks/.classpath @@ -7,6 +7,7 @@ + diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 6f3b16692..51487fe03 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -56,11 +56,6 @@ limitations under the License. android:label="MyTracks" android:exported="false" /> - - diff --git a/MyTracks/MyTracks.launch b/MyTracks/MyTracks.launch index 482b143e3..5d64dfff5 100644 --- a/MyTracks/MyTracks.launch +++ b/MyTracks/MyTracks.launch @@ -1,4 +1,4 @@ - + @@ -16,8 +16,8 @@ - + diff --git a/MyTracks/libs/google-api-client-1.2.1-alpha.jar b/MyTracks/libs/google-api-client-1.2.1-alpha.jar new file mode 100644 index 000000000..cf824376b Binary files /dev/null and b/MyTracks/libs/google-api-client-1.2.1-alpha.jar differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_chartsettings.png b/MyTracks/res/drawable-hdpi/ic_menu_chartsettings.png new file mode 100644 index 000000000..2364bd956 Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_chartsettings.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_lastsegment.png b/MyTracks/res/drawable-hdpi/ic_menu_lastsegment.png new file mode 100644 index 000000000..b7763525b Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_lastsegment.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_pauserecording.png b/MyTracks/res/drawable-hdpi/ic_menu_pauserecording.png new file mode 100644 index 000000000..f6afcf025 Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_pauserecording.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_recordtrack.png b/MyTracks/res/drawable-hdpi/ic_menu_recordtrack.png new file mode 100644 index 000000000..11d79017e Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_recordtrack.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_sensorstatus.png b/MyTracks/res/drawable-hdpi/ic_menu_sensorstatus.png new file mode 100644 index 000000000..b13f28cd2 Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_sensorstatus.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_statistics.png b/MyTracks/res/drawable-hdpi/ic_menu_statistics.png new file mode 100644 index 000000000..ce3d6285a Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_statistics.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_stoprecording.png b/MyTracks/res/drawable-hdpi/ic_menu_stoprecording.png new file mode 100644 index 000000000..a55f09f27 Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_stoprecording.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_tracklist.png b/MyTracks/res/drawable-hdpi/ic_menu_tracklist.png new file mode 100644 index 000000000..68338297b Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_tracklist.png differ diff --git a/MyTracks/res/drawable-hdpi/ic_menu_waypoints.png b/MyTracks/res/drawable-hdpi/ic_menu_waypoints.png new file mode 100644 index 000000000..c2c792f11 Binary files /dev/null and b/MyTracks/res/drawable-hdpi/ic_menu_waypoints.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_chartsettings.png b/MyTracks/res/drawable-ldpi/ic_menu_chartsettings.png new file mode 100644 index 000000000..e2692561d Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_chartsettings.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_lastsegment.png b/MyTracks/res/drawable-ldpi/ic_menu_lastsegment.png new file mode 100644 index 000000000..5ae3b5687 Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_lastsegment.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_pauserecording.png b/MyTracks/res/drawable-ldpi/ic_menu_pauserecording.png new file mode 100644 index 000000000..4a0723637 Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_pauserecording.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_recordtrack.png b/MyTracks/res/drawable-ldpi/ic_menu_recordtrack.png new file mode 100644 index 000000000..60f3c67e6 Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_recordtrack.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_sensorstatus.png b/MyTracks/res/drawable-ldpi/ic_menu_sensorstatus.png new file mode 100644 index 000000000..4a25d569f Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_sensorstatus.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_statistics.png b/MyTracks/res/drawable-ldpi/ic_menu_statistics.png new file mode 100644 index 000000000..ec2e4e721 Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_statistics.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_stoprecording.png b/MyTracks/res/drawable-ldpi/ic_menu_stoprecording.png new file mode 100644 index 000000000..6f0104709 Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_stoprecording.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_tracklist.png b/MyTracks/res/drawable-ldpi/ic_menu_tracklist.png new file mode 100644 index 000000000..0ba0b6bad Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_tracklist.png differ diff --git a/MyTracks/res/drawable-ldpi/ic_menu_waypoints.png b/MyTracks/res/drawable-ldpi/ic_menu_waypoints.png new file mode 100644 index 000000000..e8dda7e2a Binary files /dev/null and b/MyTracks/res/drawable-ldpi/ic_menu_waypoints.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_chartsettings.png b/MyTracks/res/drawable-mdpi/ic_menu_chartsettings.png new file mode 100644 index 000000000..38327b492 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_chartsettings.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_lastsegment.png b/MyTracks/res/drawable-mdpi/ic_menu_lastsegment.png new file mode 100644 index 000000000..ee9c085c8 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_lastsegment.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_pauserecording.png b/MyTracks/res/drawable-mdpi/ic_menu_pauserecording.png new file mode 100644 index 000000000..37cf534b1 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_pauserecording.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_recordtrack.png b/MyTracks/res/drawable-mdpi/ic_menu_recordtrack.png new file mode 100644 index 000000000..f2327cef7 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_recordtrack.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_sensorstatus.png b/MyTracks/res/drawable-mdpi/ic_menu_sensorstatus.png new file mode 100644 index 000000000..ca4ea1899 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_sensorstatus.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_statistics.png b/MyTracks/res/drawable-mdpi/ic_menu_statistics.png new file mode 100644 index 000000000..859be5c8a Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_statistics.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_stoprecording.png b/MyTracks/res/drawable-mdpi/ic_menu_stoprecording.png new file mode 100644 index 000000000..263da50f9 Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_stoprecording.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_tracklist.png b/MyTracks/res/drawable-mdpi/ic_menu_tracklist.png new file mode 100644 index 000000000..5c86763bb Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_tracklist.png differ diff --git a/MyTracks/res/drawable-mdpi/ic_menu_waypoints.png b/MyTracks/res/drawable-mdpi/ic_menu_waypoints.png new file mode 100644 index 000000000..3b9284e8c Binary files /dev/null and b/MyTracks/res/drawable-mdpi/ic_menu_waypoints.png differ diff --git a/MyTracks/res/layout/list.xml b/MyTracks/res/layout/list.xml deleted file mode 100644 index 33567b8d2..000000000 --- a/MyTracks/res/layout/list.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/MyTracks/res/layout/mytracks_send_to_google.xml b/MyTracks/res/layout/mytracks_send_to_google.xml index a55553367..276bd58ac 100644 --- a/MyTracks/res/layout/mytracks_send_to_google.xml +++ b/MyTracks/res/layout/mytracks_send_to_google.xml @@ -42,7 +42,7 @@ limitations under the License. android:layout_marginTop="5dip" android:layout_marginLeft="5dip"> - - - - - - diff --git a/MyTracks/res/menu/main.xml b/MyTracks/res/menu/main.xml index 8ff5a9e71..e2a2dc395 100644 --- a/MyTracks/res/menu/main.xml +++ b/MyTracks/res/menu/main.xml @@ -1,19 +1,19 @@ diff --git a/MyTracks/res/values-ca/strings.xml b/MyTracks/res/values-ca/strings.xml index 1a001c3e2..b7b3376b1 100644 --- a/MyTracks/res/values-ca/strings.xml +++ b/MyTracks/res/values-ca/strings.xml @@ -54,7 +54,6 @@ limitations under the License. S\'ha enviat la ruta a maps.google.com. S\'ha enviat la ruta a un full de càlcul a docs.google.com. S\'ha enviat la ruta a docs.google.com. - Poden passar uns minuts abans que les rutes noves apareguin. 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. Nota: Es mostra una ubicació determinada a partir de les xarxes sense fils i antenes telefòniques. 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. @@ -104,6 +103,10 @@ limitations under the License. Error Fet Punt de referència + Cancel·la + Tanca + Edita + Desa (Inici) @@ -241,28 +244,6 @@ limitations under the License. Satèl·lits GPS - - Seleccioneu un mapa del vostre compte: - Crea un mapa nou... - Envia a Google My Maps - S\'estan carregant els mapes... - No s\'han pogut carregar els vostres mapes en aquest moment - Selecciona aquest mapa - Edita els detalls del mapa - Comparteix el mapa amb amics - Comparteix la ruta amb amics - Elimina el mapa del vostre compte - Títol del mapa: - Descripció del mapa: - Fes que aquest mapa i els seus continguts apareguen als resultats de cerca - 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 - Aquest mapa i els seus continguts es mostraran als resultats de cerca - 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. - Tanca - Edita - Cancel·la - Desa - M\'agradaria compartir un mapa amb tu Crec que estaràs interessat en aquest mapa: @@ -272,6 +253,7 @@ limitations under the License. Crec que estaràs interessat en aquesta ruta. Per verue aquesta ruta fes servir Google Earth o altre programari de mapes. + Comparteix la ruta amb amics Públic diff --git a/MyTracks/res/values-de/strings.xml b/MyTracks/res/values-de/strings.xml index a17e5ac0a..893d3d5d0 100644 --- a/MyTracks/res/values-de/strings.xml +++ b/MyTracks/res/values-de/strings.xml @@ -1,398 +1,378 @@ - + - - - - Track aufzeichnen - Aufzeichnung anhalten - Satellitenansicht - Kartenansicht - Mein Aufenthaltsort - Wegpunkte - Alle Tracks löschen - Einstellungen - Höhe - Bitte wähle einen Track zur Ansicht aus - Aktueller Track - Letztes Segment - Sensorstatus - Aggregierte Statistiken - - - Es wurde kein Dienst zur Positionsermittlung auf diesem Gerät gefunden. - Es wurde kein GPS-Empfänger auf diesem Gerät gefunden. - Beim Senden des Tracks an Google MyMaps ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. - Beim Senden des Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. - Start der Aufzeichnung fehlgeschlagen. Bitte versuche es noch einmal. - Anhalten unmöglich. Bitte versuche es noch einmal. - Konnte Datei nicht lesen. - Datei ist nicht im GPX-Format. - Ein Fehler ist aufgetreten. Bitte versuche es noch einmal. - Track existiert nicht. - Die Datei ist zu groß (maximal 500 KB). Operation abgebrochen. - Der Wegpunkt kann nicht einfügt werden. Keine Positionsbestimmung möglich. - - - Warte auf ein besseres GPS-Signal. Bitte stelle sicher, dass der Himmel sichtbar ist. - Ein neuer Track wird aufgezeichnet. - Die Aufzeichnung wurde angehalten. - Eine neue Karte wurde auf maps.google.com erstellt. - Dein Track wurde auf maps.google.com gespeichert. - Dein Track wurde in einer neuen Tabelle bei docs.google.com gespeichert. - Dein Track wurde an docs.google.com gesendet. - Manchmal dauert es ein paar Minuten, bis neue Tracks bei maps.google.com erscheinen. - 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. - Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Mobilfunkstationen und WiFi-Netzwerken. - Die Position kann derzeit nicht bestimmt werden. Aktiviere mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stelle sicher, dass der Himmel sichtbar ist. - Ein Wegpunkt wurde eingefügt. - Ein Statistikwegpunkt wurde eingefügt. - - - Bitte warten… - Dein Track wird an Google gesendet... - Lese Track... - Bereite Track vor... - Anmeldung bei Google Maps... - Anmeldung bei Google Docs... - Track wird an Google Docs gesendet... - Track wird an Google Maps gesendet... - Neue \'Meine Karte\' wird erstellt... - Importiere GPS-Track. Das kann ein wenig dauern. - Schreibe Datei auf SD-Karte. Das kann ein wenig dauern. - Aktualisiere Trackstatistik... - - - Bist Du Dir wirklich sicher? - Alle Tracks und Wegpunkte werden endgültig von diesem Gerät gelöscht. - Dieser Track und seine Wegpunkte werden endgültig von diesem Gerät gelöscht. - Dieser Wegpunkt wird endgültig von diesem Gerät gelöscht. - - - Erstellt von My Tracks für Android. - Erstellt von <a href=\'http://mytracks.appspot.com\'>My Tracks</a> für Android. - Neue Karte - - - Punkte - Aufgezeichnet am - Aktivität - Art des Wegpunktes - (Teil %d) - - - Dein Track wird aufgezeichnet… - - - Nein - Ja - Fehler - Erfolgreich - Wegpunkt - - - (Beginn) - (Ende) - - - Anzeigen - An eine Google MyMaps-Karte senden - An eine neue Google MyMaps-Karte senden - An Google Docs senden - An Google senden… - Vom Gerät löschen… - Bearbeiten - Auf SD-Karte speichern… - GPX-Datei exportieren - KML-Datei exportieren - CSV-Datei exportieren - TCX-Datei exportieren - An Google MyMaps senden - Alle Tracks löschen - - Alle Tracks exportieren - Exportieren beendet - Einen Track auswählen. Lange berühren für mehr Optionen. - Alle Tracks - Dieser Track - Mit Freunden teilen… - Ein Link zur Google MyMaps-Karte - Eine GPX-Datei - Eine KML-Datei - Eine CSV-Datei - Eine TCX-Datei - Karte zurücksetzen. - - - Alle Wegpunkte löschen - Dieser Wegpunkt - Löschen - Details bearbeiten - Auf der Karte zeigen - Wegpunkt einfügen - Statistik einfügen - Wegpunkt auswählen. Lange berühren für mehr Optionen. - - - Trackdetails - Name - Beschreibung - Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…) - - - Wegpunktdetails - Wegpunktname - Wegpunktbeschreibung - Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…) - - - Einstellungen für My Tracks - Anzeige - Aufzeichnung - Mit Freunden teilen - - In dieser Android-Version nicht verfügbar +--> + - Dauer zwischen Ansagen - Kürzeste Zeit zwischen zwei Ansagen - - Kleinste Entfernung zwischen Punkten - Kleinste Entfernung zwischen zwei Positionsaufzeichnungen - - Neues Segment, wenn Punkte weiter entfernt als - Der Track wird in zwei geteilt, wenn zwei folgende Punkte weiter entfernt sind als hier angegeben. - - Aufzeichnungsfrequenz - Kürzeste Zeit zwischen zwei Positionsaufzeichnungen - - Aufzeichnung pausieren, wenn Genauigkeit kleiner als - Die Aufzeichnung wird kurzfristig angehalten, wenn Deine Position nicht genauer als hier angegeben ermittelt werden kann. - - Entfernungen in metrischen Einheiten anzeigen - Alle Entfernungen werden in metrischen Einheiten angezeigt. - Alle Entfernungen werden in imperialen Längeneinheiten angezeigt. - - Karten auf maps.google.com veröffentlichen - Hochgeladene Karten auf maps.google.com sind für alle sichtbar. - Hochgeladene Karten auf maps.google.com werden nicht öffentlich sichtbar aufgelistet. - - Geschwindigkeitsanzeige - Geschwindigkeit wird angezeigt. - Pace wird angezeigt. - - Nur URL als Einladung zum Teilen senden - Beim Teilen mit Freunden wird nur die Karten-URL gesendet. - Beim Teilen mit Freunden wird der GPS-Track selbst gesendet. - - Zerteilungsfrequenz - Längste Dauer oder Entfernung, nach der automatisch ein Marker eingefügt wird. + + Track aufzeichnen + Aufzeichnung anhalten + Satellitenansicht + Kartenansicht + Mein Aufenthaltsort + Wegpunkte + Alle Tracks löschen + Einstellungen + Höhe + Bitte wähle einen Track zur Ansicht aus + Aktueller Track + Letztes Segment + Sensorstatus + Aggregierte Statistiken - Zeitbegrenzung der Auto-Wiederaufnahme - Maximale Zeit in Minuten, nach der eine abgebrochene Aufzeichnung nach einem Telefonneustart wiederaufgenommen wird. - - Zeitstempel als Trackname - Der Trackname wird aus Datum und Zeit zum Aufzeichnungsbeginn zusammengesetzt. - Tracknamen erhalten eine fortlaufende Nummer. - - Pace - - Wird nur unter Android 2.0 oder höher unterstützt - Sensortyp - Der Typ des zu verbindenden Sensors - Sensor-Id - Die zum Sensor zu sendende Id - Die Id muss eine Ganzzahl sein. - Bluetooth-Sensor - Der zu verbindende Bluetoothsensor - Art der Datenanzeige - - Datensicherung (BETA) - SD-Karten-Sicherung - Jetzt sichern - Eine Kopie aller Deiner Tracks und Einstellungen auf die SD-Karte schreiben - Jetzt wiederherstellen - Alle Tracks und Einstellungen von der SD-Karte holen. - Während ein Track aufgezeichnet wird, kann nicht mit der Datensicherung gearbeitet werden. - - - Eine Wiederherstellung von der SD-Karte ÜBERSCHREIBT alle vorhandenen Tracks und Einstellungen. Willst Du das wirklich? - Wähle die wiederherzustellende Datensicherung aus - Keine Datensicherungen gefunden. - Schreibe alle Tracks und Einstellungen auf die SD-Karte. Dies kann eine Weile dauern... - Lese alle Tracks und Einstellungen von der SD-Karte. Dies kann eine Weile dauern... - - - GPS-Satelliten - - - Wähle eine Karte von Deinem Konto aus - Neue Karte erstellen… - An Google My Maps senden - Karten werden geladen… - Deine Karten konnten im Augenblick nicht geladen werden! - Diese Karte auswählen - Kartendetails bearbeiten - Karte mit Freunden teilen - Track mit Freunden teilen - Karte vom Konto löschen - Kartentitel - Trackbeschreibung - Diese Karte und ihren Inhalt in Suchergebnissen sichtbar machen. - Diese Karte und ihren Inhalt nicht in Suchergebnissen sichtbar machen. - Diese Karte und ihr Inhalt sind in Suchergebnissen sichtbar. - Weder diese Karte noch ihr Inhalt sind in Suchergebnissen sichtbar, allerdings kann sie trotzdem von anderen durch einen direkten Link angezeigt werden. - Schließen - Bearbeiten + + Es wurde kein Dienst zur Positionsermittlung auf diesem Gerät gefunden. + Es wurde kein GPS-Empfänger auf diesem Gerät gefunden. + Beim Senden des Tracks an Google Fusion Tables ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. + Beim Senden des Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. + Start der Aufzeichnung fehlgeschlagen. Bitte versuche es noch einmal. + Anhalten unmöglich. Bitte versuche es noch einmal. + Konnte Datei nicht lesen. + Datei ist nicht im GPX-Format. + Ein Fehler ist aufgetreten. Bitte versuche es noch einmal. + Track existiert nicht. + Die Datei ist zu groß (maximal 500 KB). Operation abgebrochen. + Der Wegpunkt kann nicht einfügt werden. Keine Positionsbestimmung möglich. + + + Warte auf ein besseres GPS-Signal. Bitte stelle sicher, dass der Himmel sichtbar ist. + Ein neuer Track wird aufgezeichnet. + Die Aufzeichnung wurde angehalten. + Eine neue Tabelle wurde auf google.com/fusiontables erstellt. + Dein Track wurde auf maps.google.com gespeichert. + Dein Track wurde in einer neuen Tabelle bei docs.google.com gespeichert. + Dein Track wurde an docs.google.com gesendet. + 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. + Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Mobilfunkstationen und WiFi-Netzwerken. + Die Position kann derzeit nicht bestimmt werden. Aktiviere mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stelle sicher, dass der Himmel sichtbar ist. + Ein Wegpunkt wurde eingefügt. + Ein Statistikwegpunkt wurde eingefügt. + + + Bitte warten… + Dein Track wird an Google gesendet... + Lese Track... + Bereite Track vor... + Anmeldung bei Google Fusion Tables... + Anmeldung bei Google Docs... + Track wird an Google Docs gesendet... + Track wird an Google Fusion Tables gesendet... + Neue \'Fusion Tabelle\' wird erstellt... + Importiere GPS-Track. Das kann ein wenig dauern. + Schreibe Datei auf SD-Karte. Das kann ein wenig dauern. + Aktualisiere Trackstatistik... + + + Bist Du Dir wirklich sicher? + Alle Tracks und Wegpunkte werden endgültig von diesem Gerät gelöscht. + Dieser Track und seine Wegpunkte werden endgültig von diesem Gerät gelöscht. + Dieser Wegpunkt wird endgültig von diesem Gerät gelöscht. + + + Erstellt von My Tracks für Android. + Erstellt von <a href=\'http://mytracks.appspot.com\'>My Tracks</a> für Android. + Neue Karte + + + Punkte + Aufgezeichnet am + Aktivität + Art des Wegpunktes + (Teil %d) + + + Dein Track wird aufgezeichnet… + + + Nein + Ja + Fehler + Erfolgreich + Wegpunkt Abbrechen + Bearbeiten Sichern - - Ich möchte gerne eine Karte mit Dir teilen. + + (Beginn) + (Ende) + + + Anzeigen + An eine Google MyMaps-Karte senden + An eine neue Google MyMaps-Karte senden + An Google Docs senden + An Google senden… + Vom Gerät löschen… + Bearbeiten + Auf SD-Karte speichern… + GPX-Datei exportieren + KML-Datei exportieren + CSV-Datei exportieren + TCX-Datei exportieren + An Google MyMaps senden + Alle Tracks löschen + + Alle Tracks exportieren + Exportieren beendet + Einen Track auswählen. Lange berühren für mehr Optionen. + Alle Tracks + Dieser Track + Mit Freunden teilen… + Ein Link zur Google Fusion Tables-Karte + Eine GPX-Datei + Eine KML-Datei + Eine CSV-Datei + Eine TCX-Datei + Karte zurücksetzen. + + + Alle Wegpunkte löschen + Dieser Wegpunkt + Löschen + Details bearbeiten + Auf der Karte zeigen + Wegpunkt einfügen + Statistik einfügen + Wegpunkt auswählen. Lange berühren für mehr Optionen. + + + Trackdetails + Name + Beschreibung + Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…) + + + Wegpunktdetails + Wegpunktname + Wegpunktbeschreibung + Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…) + + + Einstellungen für My Tracks + Anzeige + Aufzeichnung + Mit Freunden teilen + + In dieser Android-Version nicht verfügbar + + Dauer zwischen Ansagen + Kürzeste Zeit zwischen zwei Ansagen + + Kleinste Entfernung zwischen Punkten + Kleinste Entfernung zwischen zwei Positionsaufzeichnungen + + Neues Segment, wenn Punkte weiter entfernt als + Der Track wird in zwei geteilt, wenn zwei folgende Punkte weiter entfernt sind als hier angegeben. + + Aufzeichnungsfrequenz + Kürzeste Zeit zwischen zwei Positionsaufzeichnungen + + Aufzeichnung pausieren, wenn Genauigkeit kleiner als + Die Aufzeichnung wird kurzfristig angehalten, wenn Deine Position nicht genauer als hier angegeben ermittelt werden kann. + + Entfernungen in metrischen Einheiten anzeigen + Alle Entfernungen werden in metrischen Einheiten angezeigt. + Alle Entfernungen werden in imperialen Längeneinheiten angezeigt. + + Karten auf maps.google.com veröffentlichen + Hochgeladene Karten auf maps.google.com sind für alle sichtbar. + Hochgeladene Karten auf maps.google.com werden nicht öffentlich sichtbar aufgelistet. + + Geschwindigkeitsanzeige + Geschwindigkeit wird angezeigt. + Pace wird angezeigt. + + Nur URL als Einladung zum Teilen senden + Beim Teilen mit Freunden wird nur die Karten-URL gesendet. + Beim Teilen mit Freunden wird der GPS-Track selbst gesendet. + + Zerteilungsfrequenz + Längste Dauer oder Entfernung, nach der automatisch ein Marker eingefügt wird. + + Zeitbegrenzung der Auto-Wiederaufnahme + Maximale Zeit in Minuten, nach der eine abgebrochene Aufzeichnung nach einem Telefonneustart wiederaufgenommen wird. + + Zeitstempel als Trackname + Der Trackname wird aus Datum und Zeit zum Aufzeichnungsbeginn zusammengesetzt. + Tracknamen erhalten eine fortlaufende Nummer. + + Pace + + Wird nur unter Android 2.0 oder höher unterstützt + Sensortyp + Der Typ des zu verbindenden Sensors + Sensor-Id + Die zum Sensor zu sendende Id + Die Id muss eine Ganzzahl sein. + Bluetooth-Sensor + Der zu verbindende Bluetoothsensor + Art der Datenanzeige + + Datensicherung (BETA) + SD-Karten-Sicherung + Jetzt sichern + Eine Kopie aller Deiner Tracks und Einstellungen auf die SD-Karte schreiben + Jetzt wiederherstellen + Alle Tracks und Einstellungen von der SD-Karte holen. + Während ein Track aufgezeichnet wird, kann nicht mit der Datensicherung gearbeitet werden. + + + Eine Wiederherstellung von der SD-Karte ÜBERSCHREIBT alle vorhandenen Tracks und Einstellungen. Willst Du das wirklich? + Wähle die wiederherzustellende Datensicherung aus + Keine Datensicherungen gefunden. + Schreibe alle Tracks und Einstellungen auf die SD-Karte. Dies kann eine Weile dauern... + Lese alle Tracks und Einstellungen von der SD-Karte. Dies kann eine Weile dauern... + + + GPS-Satelliten + + + Karte mit Freunden teilen + Ich möchte gerne eine Karte mit Dir teilen. Vielleicht interessierst Du Dich für diese Karte: - - %s - Ich möchte einen GPS-Track mit Dir teilen + + %s + Ich möchte einen GPS-Track mit Dir teilen Vielleicht interessierst Du Dich für diese Track: - + %s. - + Track mit Freunden teilen + Öffentlich Nicht in Suchergebnissen enthalten. Du hast noch keine Karten. - - - Durchschnitt in Bew. - Pace in Bew. - - - Durchschnittsgeschw. - Durchschnitts-Pace - - Höhenprofil - Höhengewinn - Höhe - Breitengrad - Längengrad - - - Niedrigste Höhenlage - Minimale Steigung - Minimaler Pace - - - Höchste Höhenlage - Maximale Steigung - Max. Geschwindigkeit - - Lade Track… - - - Diagramm-Einstellungen - - X-Achse - Nach Entfernung - Nach Zeit - - - Y-Achse - - - - Stunde - Stunden - Std - Minute - Minuten - Sekunde - Sekunden - - - Fuß - Kilometer - Meilen - - - Kilometer pro Stunde - pro Kilometer - Meilen pro Stunde - pro Meile - - - Zeit in Bewegung - Gesamtzeit - Zeit - SD-Karte nicht verfügbar - Geschwindigkeit - Du musst erst eine Aufzeichnung beginnen. - Statistik - Gesamtentfernung - Entfernung - Energie - Kadenz - Herzfrequenz - Höchste Herzfrequenz - Bitte warten… - - - Lizenzvereinbarung für Endverbraucher - Akzeptieren - Ablehnen - - - Über - Hilfe - Unterstützung - - - Senden an… - Jetzt senden - Neue Karte erstellen - Eine Karte auswählen - Trackstatistik senden - Statistik und Punkte senden - - - Kein Konto vorhanden - Kein Konto gefunden, um Tracks zu versenden. - Wähle ein Konto - - - Keine externes Speichermedium verfügbar. Stelle sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird. - Die Datei kann nicht geschrieben werden. - Die Datei konnte nicht gelesen werden. - Das Verzeichnis auf der SD-Karte kann nicht erstellt werden. - Der Track wurde auf die SD-Karte geschrieben. - Lesen von der SD-Karte beendet. - - - Suche nach neuer Version - Jetzt aktualisieren - Fortfahren, ohne zu aktualisieren - Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisiere %1$s. - 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. - 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. - - - Willkommen - Über My Tracks - - - My Tracks keine Entfernung + + + Durchschnitt in Bew. + Pace in Bew. + + + Durchschnittsgeschw. + Durchschnitts-Pace + + Höhenprofil + Höhengewinn + Höhe + Breitengrad + Längengrad + + + Niedrigste Höhenlage + Minimale Steigung + Minimaler Pace + + + Höchste Höhenlage + Maximale Steigung + Max. Geschwindigkeit + + Lade Track… + + + Diagramm-Einstellungen + + X-Achse + Nach Entfernung + Nach Zeit + + + Y-Achse + + + + Stunde + Stunden + Std + Minute + Minuten + Sekunde + Sekunden + + + Fuß + Kilometer + Meilen + + + Kilometer pro Stunde + pro Kilometer + Meilen pro Stunde + pro Meile + + + Zeit in Bewegung + Gesamtzeit + Zeit + SD-Karte nicht verfügbar + Geschwindigkeit + Du musst erst eine Aufzeichnung beginnen. + Statistik + Gesamtentfernung + Entfernung + Energie + Kadenz + Herzfrequenz + Höchste Herzfrequenz + Bitte warten… + + + Lizenzvereinbarung für Endverbraucher + Akzeptieren + Ablehnen + + + Über + Hilfe + Unterstützung + + + Senden an… + Jetzt senden + Trackstatistik senden + Statistik und Punkte senden + + + Kein Konto vorhanden + Kein Konto gefunden, um Tracks zu versenden. + Wähle ein Konto + + + Keine externes Speichermedium verfügbar. Stelle sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird. + Die Datei kann nicht geschrieben werden. + Die Datei konnte nicht gelesen werden. + Das Verzeichnis auf der SD-Karte kann nicht erstellt werden. + Der Track wurde auf die SD-Karte geschrieben. + Lesen von der SD-Karte beendet. + + + Suche nach neuer Version + Jetzt aktualisieren + Fortfahren, ohne zu aktualisieren + Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisiere %1$s. + 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. + 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. + + + Willkommen + Über My Tracks + + + My Tracks keine Entfernung - My Tracks %1$s %2$.2f %3$s in %4$s mit %5$s %6$s - - - Einheitenbenachrichtigung - Voreingestellt sind metrische Einheiten. Willst Du diese Einstellung ändern? - - - Bluetootheinstellungen - Geräteverbindung - Öffnet Androids Bluetootheinstellungen, um Geräte zu verbinden. - Sensoreinstellungen - - - Verbunden mit %s - Verbinde mit Bluetoothsensor ... - Bluetoothsensor nicht verbunden. - - - Sensor Data Time - Energiemessung aktiviert - Herzfrequenzmessung aktiviert - Keine Sensoren verfügbar - Aktiviert - Verbinde... - Verbunden - Getrennt - Kein Sensor-Dienst - Suche... - Sende... - - - Track-Aufzeichnungs-Benachrichtigungen empfangen - Aufgezeichnete Trackdaten lesen - Aufgezeichnete Trackdaten schreiben, Starten/Anhalten des Aufzeichnens von Tracks - Werde benachrichtigt, wenn die Aufzeichnung eines Tracks beginnt oder anhält. - Ermöglicht das Lesen von Trackdaten wie Messpunkten oder Wegpunkten. - Ermöglicht das Schreiben von Trackdaten wie beim Starten der Aufzeichnung oder beim Hinzufügen von Wegpunkten. - + --> + My Tracks %1$s %2$.2f %3$s in %4$s mit %5$s %6$s + + + Einheitenbenachrichtigung + Voreingestellt sind metrische Einheiten. Willst Du diese Einstellung ändern? + + + Bluetootheinstellungen + Geräteverbindung + Öffnet Androids Bluetootheinstellungen, um Geräte zu verbinden. + Sensoreinstellungen + + + Verbunden mit %s + Verbinde mit Bluetoothsensor ... + Bluetoothsensor nicht verbunden. + + + Sensor Data Time + Energiemessung aktiviert + Herzfrequenzmessung aktiviert + Keine Sensoren verfügbar + Aktiviert + Verbinde... + Verbunden + Getrennt + Kein Sensor-Dienst + Suche... + Sende... + + + Track-Aufzeichnungs-Benachrichtigungen empfangen + Aufgezeichnete Trackdaten lesen + Aufgezeichnete Trackdaten schreiben, Starten/Anhalten des Aufzeichnens von Tracks + Werde benachrichtigt, wenn die Aufzeichnung eines Tracks beginnt oder anhält. + Ermöglicht das Lesen von Trackdaten wie Messpunkten oder Wegpunkten. + Ermöglicht das Schreiben von Trackdaten wie beim Starten der Aufzeichnung oder beim Hinzufügen von Wegpunkten. + diff --git a/MyTracks/res/values-es/strings.xml b/MyTracks/res/values-es/strings.xml index 7a58e15e8..7f0ec7670 100644 --- a/MyTracks/res/values-es/strings.xml +++ b/MyTracks/res/values-es/strings.xml @@ -51,7 +51,6 @@ limitations under the License. Su recorrido ha sido enviado a maps.google.com. Su recorrido ha sido enviado a una nueva hoja de cálculo en docs.google.com. Su recorrido ha sido enviado a docs.google.com. - A veces nuevos recorridos pueden tardar algunos minutos en aparecer. 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. Nota: Una ubicación aproximada es mostrada, basada en informacion de torres de telefonía celular y red WIFI. 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. @@ -98,6 +97,7 @@ limitations under the License. Sí Éxito Señal de recorrido + Cancelar (Inicio) @@ -193,30 +193,10 @@ limitations under the License. Satélites GPS - - - Elija un mapa de su cuenta: - Crear mapa nuevo... - Enviar a Google My Maps - Cargando mapas... - Sus mapas no pudieron ser cargados en este momento! - Seleccione este mapa - Editar detalles del mapa - Compartir mapa con amigos Compartir recorrido con amigos - Borrar mapa de su cuenta - Título del mapa: - Descripción del mapa: - Permitir que este mapa y su contenido sean mostrados en resultados de búsqueda - No permitir que este mapa y su contenido sean mostrados en resultados de búsqueda, pero permitir acceso directo mediante su URL - Este mapa y su contenido serán mostrados en resultados de búsqueda - No mostrar ni este mapa ni sus contenidos en resultados de búsqueda, pero permitir acceso directo mediante su URL - Cerrar - Editar - Cancelar - Guardar + Compartir mapa con amigos Me gustaría compartir un mapa contigo Creo que este mapa pudiera interesarte: @@ -225,6 +205,7 @@ limitations under the License. Creo que este recorrido pudiera interesarte. Para ver este recorrido usa Google Earth o algún otro software de mapeo. + Compartir recorrido con amigos Público @@ -315,8 +296,6 @@ limitations under the License. Enviar a... Enviar ahora - Crear un nuevo mapa - Escoja un mapa existente Enviar estadísticas de recorrido Enviar estadísticas y puntos diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml index bbe44c007..bf588b041 100644 --- a/MyTracks/res/values-fr/strings.xml +++ b/MyTracks/res/values-fr/strings.xml @@ -53,7 +53,6 @@ limitations under the License. Votre parcours a été envoyé à maps.google.com. Votre parcours a été envoyé dans un tableur à docs.google.com. Votre parcours a été envoyé à docs.google.com. - Votre parcours peut prendre quelques minutes avant d\'être visible. 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. Note : Votre position est approximative, elle a été déterminée par les antennes de téléphonie ou le réseau WiFi. 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. @@ -100,6 +99,10 @@ limitations under the License. Oui Erreur Point de passage + Annuler + Fermer + Modifier + Enregistrer (Début) @@ -194,29 +197,8 @@ limitations under the License. Satellites GPS - - Choisissez une carte de votre compte : - Création d\'une nouvelle carte en cours... - Envoyer à Google Mes cartes - Chargements de vos cartes... - Vos cartes n\'ont pu être chargées - Sélectionnez la carte - Consulter les détails de la carte - Partager. - Partager des parcours. - Supprimer la carte de votre compte. - Titre de la carte : - Description de la carte : - Rendre cette carte et son contenu visibles dans les résultats de recherche. - 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 - Cette carte et son contenu seront visibles dans les résultats de recherche - Ni cette carte, ni son contenu seront visible dans les résultats de recherche, mais ils seront consultables en utilisant l\'adresse URL exacte - Fermer - Modifier - Annuler - Enregistrer - + Partager. Je voudrais partager ma carte avec vous Je pense que cette carte vous intéressera : @@ -225,6 +207,7 @@ limitations under the License. Je pense que ce parcours vous intéressera. Pour voir ce parcours, utilisez Google Earth ou tout autre logiciel de cartographie. + Partager des parcours. Carte non listée diff --git a/MyTracks/res/values-it/strings.xml b/MyTracks/res/values-it/strings.xml index 2fe4ff4ba..0deba74fe 100644 --- a/MyTracks/res/values-it/strings.xml +++ b/MyTracks/res/values-it/strings.xml @@ -54,7 +54,6 @@ limitations under the License. Il tuo percorso è stato inviato a maps.google.com. Il tuo percorso è stato inviato ad un nuovo foglio di calcolo su docs.google.com. Il tuo percorso è stato inviato a docs.google.com. - Potrebbe essere necessario aspettare qualche minuto prima che i nuovi percorsi siano visibili. Premi qui per andare a «Impostazioni di posizione e protezione» e attiva «Utilizza satelliti GPS». Poi premi il tasto di ritorno per tornare qui. Nota: la posizione indicata è un\'approssimazione determinata in base alle antenne cellulari e reti WIFI vicine. 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. @@ -104,6 +103,10 @@ limitations under the License. Errore Successo Punto di passaggio + Annulla + Chiudi + Modifica + Salva (Inizio) @@ -200,28 +203,6 @@ limitations under the License. Satelliti GPS - - Scegli una mappa dal tuo account: - Crea una nuova mappa... - Invia a «Le mie mappe» di Google - Caricamento mappe in corso... - Le tue mappe non possono momentaneamente essere caricate! - Scegli questa mappa - Modifica dettagli della mappa - Condividi la mappa con amici - Condividi il percorso con amici - Elimina la mappa dal tuo account - Titolo della mappa: - Descrizione della mappa: - Rendi questa mappa e il suo contenuto visibili fra i risultati di ricerche - Rendi questa mappa e il suo contenuto invisibili fra i risultati di ricerche. Resteranno comunque visibili ad altri utilizzando l\'indirizzo URL esatto - Questa mappa e il suo contenuto saranno visibili fra i risultati di ricerche - Né questa mappa né il suo contenuto saranno visibili in risultati di ricerche, ma saranno comunque visibili ad altri tramite un link diretto. - Chiudi - Modifica - Annulla - Salva - Vorrei condividere una mappa con te Credo che questa mappa potrebbe interessarti: @@ -231,6 +212,7 @@ limitations under the License. Credo che questo percorso potrebbe interessarti. Per vedere questo percorso, usa Google Earth o un altro software di cartografia. + Condividi il percorso con amici Pubblica diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml index 6a5745f77..9f80638dc 100644 --- a/MyTracks/res/values-ja/strings.xml +++ b/MyTracks/res/values-ja/strings.xml @@ -52,7 +52,6 @@ limitations under the License. トラックを maps.google.com に送信しました. トラックを docs.google.com の新しいスプレッドシートに送信しました. トラックを docs.google.com に送信しました. - 新しいトラックが見えるようになるまで数分程度かかります. ここをタップして,セキュリティ &位置情報 を開いて,「GPS機能をONにする」にチェックして下さい.バックボタンで戻ってください. ご注意: 携帯電話中継局とWi-Fiネットワークから大まかな位置を特定します. 現在位置決定ができません.位置取得源を増やし(セキュリティ&位置情報をご覧ください),空が見えるようにして下さい.もう一度試して下さい. @@ -102,6 +101,10 @@ limitations under the License. エラー 成功 ウェイポイント + キャンセル + 閉じる + 編集 + 保存 (開始) @@ -201,30 +204,6 @@ limitations under the License. GPS衛星 - - ご使用のアカウントからのマップ選択: - 新規マップ... - Googleマイマップへの送信 - マップの読み込み中... - マップを読み込めませんでした! - このマップを選択 - マップ詳細の編集 - マップの共有 - トラックの共有 - アカウントからマップの削除 - マップタイトル: - マップ説明: - - - このマップとコンテンツを検索結果に表示させる - このマップとコンテンツはどちらも検索結果に表示させない(直接URLで他の方が見ることは可能) - このマップとコンテンツは検索結果に表示されます - このマップとコンテンツはどちらも検索結果に表示されませんが,直接リンクで他の方が見ることができます. - 閉じる - 編集 - キャンセル - 保存 - マップを共有したいと思います このマップに興味を持って頂けると思います: @@ -234,6 +213,7 @@ limitations under the License. このトラックに興味を持って頂けると思います. このトラックを閲覧するには,Google Earthまたは他のマッピングソフトウェアが必要です. + トラックの共有 公開 diff --git a/MyTracks/res/values-pl/strings.xml b/MyTracks/res/values-pl/strings.xml index 017176bb3..8a639cbdf 100644 --- a/MyTracks/res/values-pl/strings.xml +++ b/MyTracks/res/values-pl/strings.xml @@ -53,7 +53,6 @@ limitations under the License. Twoja trasa został wysłana do maps.google.com. Twoja trasa została wysłana do nowego arkusza na docs.google.com. Twoja trasa została wysłana do docs.google.com. - Trasa będzie widoczna za kilka minut. 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ć. Uwaga: używana jest przybliżona lokalizacja ustalona za pomocą stacji komórkowych i sieci WIFI. 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. @@ -103,6 +102,10 @@ limitations under the License. Błąd Powodzenie Punkt orientacyjny + Anuluj + Zamknij + Edytuj + Zapisz (Początek) @@ -233,28 +236,6 @@ limitations under the License. Satelity GPS - - Wybierz mapę z twojego konta: - Utwórz nową mapę... - Wyślij do Moich Map Google - Wczytywanie map... - Nie można wczytać twoich map! - Wybierz tę mapę - Edytuj szczegóły mapy - Podziel się mapą z przyjaciółmi - Podziel się trasą z przyjaciółmi - Usuń mapę z twojego konta - Tytuł mapy: - Opis mapy: - Pokazuj tę pamę i jej zawartość w wynikach wyszukiwania - Nie pokazuj tej mapy i jej zawartości w wynikach wyszukiwania. Dostęp do mapy będzie ciągle możliwy poprzez odnośnik - Ta mapa i jej zawartość będzie dostępna w wynikach wyszukiwania - Mapa i jej zawartość nie będzie dostępna w wynikach wyszukiwania, ale będzie dostępna przez bezpośredni odnośnik - Zamknij - Edytuj - Anuluj - Zapisz - Chciałbym się z Tobą podzielić mapą Myślę, że może Cię zainteresować ta mapa: @@ -264,6 +245,7 @@ limitations under the License. Myślę, że może Cię zainteresować ta trasa. Trasę możesz obejrzeć w Google Earth lub innym programie do obsługi map. + Podziel się trasą z przyjaciółmi Publiczna diff --git a/MyTracks/res/values-pt-rBR/strings.xml b/MyTracks/res/values-pt-rBR/strings.xml index f84c710ce..e3ecbfa89 100644 --- a/MyTracks/res/values-pt-rBR/strings.xml +++ b/MyTracks/res/values-pt-rBR/strings.xml @@ -52,7 +52,6 @@ limitations under the License. Seu caminho foi enviado para maps.google.com. Seu caminho foi enviado para uma nova planilha em docs.google.com. Seu caminho foi enviado para docs.google.com. - "Pode demorar alguns minutor para que novos caminhos apareçam. "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." Nota: Uma localização aproximada, determinada por torres de telefonia celular, está sendo mostrada. 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. @@ -101,6 +100,10 @@ limitations under the License. Erro Successo Ponto de passagem + Cancelar + Fechar + Editar + Salvar (Início) @@ -184,28 +187,6 @@ limitations under the License. Satélites de GPS - - Escolha um mapa da sua conta: - Criar novo mapa... - Enviar para o Google Meus Mapas - Carregando mapas... - Seus mapas não puderam ser carregados neste momento! - Selecionar este Mapa - Editar detalhes do mapa - Compartilhar mapa com amigos - Compartilhar caminho com amigos - Apagar mapa da sua conta - Título do Mapa: - Descrição do Mapa: - Fazer com que o conteúdo deste mapa apareça em resultados de buscas - 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 - Este mapa e seu conteúdo vão aparecer em resultados de buscas - 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 - Fechar - Editar - Cancelar - Salvar - Eu gostaria de compartilhar um mapa com você Eu acredito que você pode se interessar por este mapa: @@ -215,6 +196,7 @@ limitations under the License. Eu acredito que você pode se interesasr por este caminho. Para visualizar este caminho, use o Google Earth ou outro software de mapas. + Compartilhar caminho com amigos Público diff --git a/MyTracks/res/values-sv/strings.xml b/MyTracks/res/values-sv/strings.xml index 3b18a5f95..4cefa2706 100644 --- a/MyTracks/res/values-sv/strings.xml +++ b/MyTracks/res/values-sv/strings.xml @@ -53,7 +53,6 @@ limitations under the License. Ditt spår har skickats till maps.google.com. Ditt spår har skickats till ett nytt kalkylblad på docs.google.com. Ditt spår har skickats till docs.google.com. - Det kan ibland ta några minuter innan nya spår visas. 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å. OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas. 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. @@ -103,6 +102,10 @@ limitations under the License. Fel Lyckades Positionsmärke + Avbryt + Stäng + Ändra + Spara (Start) @@ -202,28 +205,6 @@ limitations under the License. GPS-satelliter - - Välj en karta från ditt konto: - Skapa en ny karta... - Skicka till Google Mina Kartor - Laddar kartor... - Dina kartor kunde inte laddas just nu! - Välj den här kartan - Ändra kartdetaljer - Dela kartan med vänner - Dela spår med vänner - Ta bort kartan från ditt konto - Karttitel: - Kartbeskrivning: - Gör så att kartan och dess innehåll visas i sökresultat - Låt inte kartan och dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk - Kartan och dess innehåll kommer att visas i sökresultat - Varken kartan eller dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk - Stäng - Ändra - Avbryt - Spara - Jag vill dela en karta med dig Jag tror att du skulle vara intresserad av den här kartan: @@ -233,6 +214,7 @@ limitations under the License. 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. + Dela spår med vänner Publik diff --git a/MyTracks/res/values-zh-rCN/strings.xml b/MyTracks/res/values-zh-rCN/strings.xml index cacf8422c..72c2576e9 100644 --- a/MyTracks/res/values-zh-rCN/strings.xml +++ b/MyTracks/res/values-zh-rCN/strings.xml @@ -56,7 +56,6 @@ limitations under the License. 你的轨迹已经发送到 maps.google.com. 你的轨迹已经作为新的电子表格发送到 docs.google.com. 你的轨迹已经发送到 docs.google.com. - 新增的轨迹可能需要等待一段时间才会显示到页面上. 请点击这里进入\'安全和位置设置\'并勾选上\'启用 GPS 卫星\'. 然后用后退键返回. 注意: 当前显示的位置是根据无线信号和无线网络计算的近似位置. 目前无法定位. 请启用更多的位置来源 (在\'安全和位置设置\'中) 并确认天空是可见的. 再重试. @@ -106,6 +105,10 @@ limitations under the License. 错误 成功 路点 + 取消 + 关闭 + 编辑 + 保存 (开始) @@ -239,28 +242,6 @@ limitations under the License. GPS 卫星 - - 从你的帐户里选择地图: - 创建新地图... - 发送到 Google My Maps - 载入地图中... - 现在无法载入你的地图! - 选择此地图 - 编辑地图详细信息 - 与朋友分享地图 - 与朋友分享轨迹 - 删除你帐号下的此地图 - 地图标题: - 地图描述: - 允许此地图及其内容出现在搜索结果中 - 不允许地图及其内容出现在搜索结果中, 但它的内容依然可以通过网址直接访问 - 此地图及其内容会出现在搜索结果中 - 此地图及其内容不会出现在搜索结果中, 但它的内容依然可以通过网址直接访问 - 关闭 - 编辑 - 取消 - 保存 - 我想与你分享一张地图 我想你可能会对这个地图感兴趣: @@ -270,6 +251,7 @@ limitations under the License. 我想你可能会对这条轨迹感兴趣. 请用 Google Earth 或其它地图软件查看此轨迹. + 与朋友分享轨迹 公开 diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index a838c30c1..fc3caca28 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -35,7 +35,7 @@ limitations under the License. No location manager was found on this device! No GPS location provider was found on this device. - There was an error while sending your track to Google My Map. Please try again later. + There was an error while sending your track to Google Fusion Tables. Please try again later. There was an error while sending your track to Google Docs. Please try again later. Unable to start recording. Try again. Unable to stop recording. Try again. @@ -50,11 +50,10 @@ limitations under the License. Waiting for a better GPS fix. Make sure the sky is visible. Now recording a new track. Stopped recording your track. - A new Google My Map has been created at maps.google.com. + A new Google Fusion table has been created at google.com/fusiontables. Your track has been sent to maps.google.com. Your track has been sent to a new spreadsheet at docs.google.com. Your track has been sent to docs.google.com. - It may sometimes take a few minutes before new tracks appear. Please tap here to go to Security and Location Settings and check \'enable GPS satellites\'. Press back button to return. Note: An approximate location as determined from cell towers and WIFI networks is shown. 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. @@ -66,11 +65,11 @@ limitations under the License. Sending your track to Google. Reading track... Preparing track... - Authenticating to Google Maps... + Authenticating to Google Fusion Tables... Authenticating to Google Docs... Sending track to Google Docs... - Sending track to Google Maps... - Creating new My Map... + Sending track to Fusion Tables... + Creating new Fusion table... Importing a GPS track. This may take a while. Writing file to SD card. This may take a while. Updating track statistics... @@ -81,10 +80,9 @@ limitations under the License. The track and its markers will be permanently deleted from this phone. The marker will be permanently deleted from this phone. - + Created by My Tracks on Android. - Created by <a href=\'http://mytracks.appspot.com\'>My Tracks</a> on Android. - New map + Created by <a href=\'http://mytracks.appspot.com\'>My Tracks</a> on Android. Points @@ -105,6 +103,10 @@ limitations under the License. Success Waypoint License + Cancel + Close + Edit + Save (Start) @@ -112,8 +114,8 @@ limitations under the License. Show - Send to Google My Maps - Send to New Google My Map + Send to Google Fusion Tables + Send to New Google Fusion Table Send to Google Docs Send to Google... Delete from phone... @@ -123,7 +125,7 @@ limitations under the License. Save a KML file Save a CSV file Save a TCX file - Send to Google My Maps + Send to Google Fusion Tables Delete all tracks Export all tracks @@ -132,7 +134,7 @@ limitations under the License. All Tracks: This Track Share with Friends... - A link to Google My Map + A link to Google Fusion Tables A GPX file A KML file A CSV file @@ -224,6 +226,7 @@ limitations under the License. The bluetooth sensor to connect to Modular Stats Display + Backup (BETA) SD Card Backup Backup now @@ -232,7 +235,6 @@ limitations under the License. Restore all tracks and settings from the SD card Cannot manipulate backups while recording a track. - Restoring a backup from the SD card will OVERWRITE all your current tracks and settings. Are you sure? Select the backup to restore No backups found. @@ -242,37 +244,17 @@ limitations under the License. GPS Satellites - - Pick a map from your account: - Create new map... - Send to Google My Maps - Loading maps... - Your maps could not be loaded at this time! - Select this Map - Edit Map Details - Share Map with Friends - Share Track with Friends - Delete Map from your Account - Map Title: - Map Description: - Make this map and its contents show up in search results - 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 - This map and its contents will show in search results - Neither this map nor its contents will show in search results, but they may still be viewed by others through a direct link - Close - Edit - Cancel - Save - - I would like to share a map with you - I think you might be interested in this map: + Share map with friends + I would like to share a track with you + I think you might be interested in this track: %s I would like to share a track with you I think you might be interested in this track. To view this track use Google Earth or other mapping software. + Share track with friends Public @@ -346,7 +328,7 @@ limitations under the License. min/mi per mile - + W rpm @@ -384,11 +366,9 @@ limitations under the License. Send to... - Google My Maps + Google Fusion Tables Google Docs Send now - Create a new map - Pick an existing map Send track statistics Send statistics and points diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java deleted file mode 100644 index d88a2ff5a..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsConstants.java +++ /dev/null @@ -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() { } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeature.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeature.java deleted file mode 100644 index dba928ac5..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeature.java +++ /dev/null @@ -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 latitudeE6 = new Vector(); - - /** - * The longitudes of the points of this feature in order, specified in - * millionths of a degree east. - */ - private final Vector longitudeE6 = new Vector(); - - /** 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); - } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeatureMetadata.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeatureMetadata.java deleted file mode 100644 index 996f4c743..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsFeatureMetadata.java +++ /dev/null @@ -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); - } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataConverter.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataConverter.java deleted file mode 100644 index 0c726f4c5..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataConverter.java +++ /dev/null @@ -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; - } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataWrapper.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataWrapper.java deleted file mode 100644 index 365edb94f..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsGDataWrapper.java +++ /dev/null @@ -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; - } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsList.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsList.java deleted file mode 100644 index 27c881efe..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsList.java +++ /dev/null @@ -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 mapsList; - private Vector publicList; - private Set 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 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(); - publicList = new Vector(); - - 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(); - - 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); - } -} diff --git a/MyTracks/src/com/google/android/apps/mymaps/MyMapsMapMetadata.java b/MyTracks/src/com/google/android/apps/mymaps/MyMapsMapMetadata.java deleted file mode 100644 index d5531cf4a..000000000 --- a/MyTracks/src/com/google/android/apps/mymaps/MyMapsMapMetadata.java +++ /dev/null @@ -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); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java index 5aeeb3e23..e8512ac3b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java +++ b/MyTracks/src/com/google/android/apps/mytracks/DialogManager.java @@ -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) { diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 6cd26c3b5..34f6a7361 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -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 diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java index 80726e5fc..b9d11009b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksConstants.java @@ -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. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java index d4ad35b14..758c0f550 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksMap.java @@ -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, diff --git a/MyTracks/src/com/google/android/apps/mytracks/SendToGoogleDialog.java b/MyTracks/src/com/google/android/apps/mytracks/SendToGoogleDialog.java index e3f5b5a60..f39be1d84 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SendToGoogleDialog.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SendToGoogleDialog.java @@ -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(); } diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java index 9d68c9c27..d185c12db 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java @@ -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; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java index cfbab6d73..45251a3b7 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WelcomeActivity.java @@ -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)); } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java index 247c2bfa2..04132b9ce 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java +++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java @@ -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"); } } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java index 6054dc9a5..6c7978b11 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/GpxTrackWriter.java @@ -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(""); + pw.format("\n", + Charset.defaultCharset().name()); pw.println(""); pw.println(" docListWrapper = new GDataWrapper(); + docListWrapper.setAuthManager(docListAuth); + docListWrapper.setRetryOnAuthFailure(true); + + GDataWrapper trixWrapper = new GDataWrapper(); + 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(""); - if (idTagIndex == -1) { - return; - } - int idTagCloseIndex = result.indexOf("", 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(""); - 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(""); - 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("'); - sb.append(StringUtils.stringAsCData(value)); - 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); - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java new file mode 100644 index 000000000..aa957adc1 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java @@ -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 distances = new Vector(); + Vector elevations = new Vector(); + DoubleBuffer elevationBuffer = new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR); + + List locations = new ArrayList(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("

" + originalDescription + "

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

"); + 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(""); + appendCoordinate(location, builder); + builder.append(""); + 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(""); + for (Location location : track.getLocations()) { + appendCoordinate(location, builder); + builder.append(' '); + } + builder.append(""); + return builder.toString(); + } + + private boolean prepareAndUploadPoints(Track track, List 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 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 prepareLocations( + Track track, Iterable locations) { + ArrayList splitTracks = new ArrayList(); + + // 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 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 wrapper = new GDataWrapper(); + wrapper.setAuthManager(auth); + wrapper.setRetryOnAuthFailure(true); + wrapper.setClient(transport); + Log.d(MyTracksConstants.TAG, "GData connection prepared: " + this.auth); + wrapper.runQuery(new QueryFunction() { + @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; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java deleted file mode 100644 index 0af4f754a..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToMyMaps.java +++ /dev/null @@ -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("

" + track.getDescription() + "

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

"); - 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 distances = new Vector(); - Vector elevations = new Vector(); - DoubleBuffer elevationBuffer = - new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR); - - List locations = new ArrayList(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("

" + originalDescription + "

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

"); - 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 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 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 prepareLocations( - Track track, Iterable locations) { - ArrayList splitTracks = new ArrayList(); - - // 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 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", "
")); - 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); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java index a09e2fd60..dfd3513ad 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java @@ -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(""); + pw.format("\n", + Charset.defaultCharset().name()); pw.print(" docListWrapper, final String name) throws IOException { + final AtomicReference idSaver = new AtomicReference(); + + boolean result = docListWrapper.runQuery(new QueryFunction() { + @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(""); + if (idTagIndex == -1) { + return; + } + int idTagCloseIndex = result.indexOf("", 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 docListWrapper, + final String title) throws IOException { + final AtomicReference idSaver = new AtomicReference(); + + boolean result = docListWrapper.runQuery(new QueryFunction() { + @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 trixWrapper, + final String spreadsheetId) throws IOException { + final AtomicReference idSaver = new AtomicReference(); + + boolean result = trixWrapper.runQuery(new QueryFunction() { + @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("") + .append(tagBuilder.build()) + .append("") + .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())); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java new file mode 100644 index 000000000..50ab27057 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java @@ -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; + +/** + *

This class builds a string of XML tags used to talk to Docs using GData. + * + *

Sample Usage: + * + * + * String tags = new DocsTagBuilder(false) + * .append("tagName", "tagValue") + * .appendLargeUnits("bigTagName", 1.0) + * .build(); + * + * + *

results in: + * + * + * + * + * + * + * @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("") + .append(StringUtils.stringAsCData(value)) + .append(""); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java index 97d7cc99e..751be06bb 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/gdata/GDataWrapper.java @@ -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 { + + 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 { + 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 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 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 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; } diff --git a/MyTracks/src/com/google/android/apps/mymaps/GeoRect.java b/MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java similarity index 97% rename from MyTracks/src/com/google/android/apps/mymaps/GeoRect.java rename to MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java index f854b55bf..6605f1453 100644 --- a/MyTracks/src/com/google/android/apps/mymaps/GeoRect.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/GeoRect.java @@ -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; } -} +} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java index 52f5dee72..4d6da6786 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/MyTracksUtils.java @@ -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() { } diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java index 76004303f..b6b8baace 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/StringUtils.java @@ -316,7 +316,7 @@ public class StringUtils implements DescriptionGenerator { + "", // Line 1 - context.getString(R.string.my_map_link), + context.getString(R.string.fusiontable_link), // Line 2 context.getString(R.string.total_distance_label), diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java index 4e90d7227..4d4ec7d8f 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java @@ -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)); } diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java index fe4c19453..cf963fff7 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java @@ -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; } diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java index 6fb122b89..d3e0fae4d 100644 --- a/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java +++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/TracksColumns.java @@ -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"; } diff --git a/MyTracksTest/.classpath b/MyTracksTest/.classpath index 7ec6a9bf6..9ede41bc3 100644 --- a/MyTracksTest/.classpath +++ b/MyTracksTest/.classpath @@ -1,6 +1,5 @@ - @@ -10,5 +9,7 @@ + + diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java new file mode 100644 index 000000000..b1402c06f --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelperTest.java @@ -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) {} + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java new file mode 100644 index 000000000..e357605b4 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsHelper_AddTrackRowTest.java @@ -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 = + "" + + "" + + "" + // We format the date here because we can't guarantee/force the timezone + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + 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( + "")); + assertTrue(docsHelper.writtenData.contains( + "")); + assertTrue(docsHelper.writtenData.contains( + "")); + + assertTrue(docsHelper.writtenData.contains( + "")); + } + + @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); + } +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java new file mode 100644 index 000000000..0145f6a9e --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilderTest.java @@ -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("", + new DocsTagBuilder(false).append("tag", "value").build()); + } + + public void testMultiAppend() { + String actual = new DocsTagBuilder(false) + .append("tag1", "value1") + .append("tag2", "value2") + .build(); + + assertEquals("" + + "", actual); + } + + public void testAppendLargeUnits_imperial() { + assertEquals("", + new DocsTagBuilder(false).appendLargeUnits("tag", 100.0).build()); + } + + public void testAppendLargeUnits_metric() { + assertEquals("", + new DocsTagBuilder(true).appendLargeUnits("tag", 100.0).build()); + } + + public void testAppendSmallUnits_imperial() { + assertEquals("", + new DocsTagBuilder(false).appendSmallUnits("tag", 100.0).build()); + } + + public void testAppendSmallUnits_metric() { + assertEquals("", + new DocsTagBuilder(true).appendSmallUnits("tag", 100.0).build()); + } +}