diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 578bd1e48..5f52f7262 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -161,7 +161,7 @@ limitations under the License.
-
+
diff --git a/MyTracks/res/layout/about.xml b/MyTracks/res/layout/about.xml
index 51f432b90..753d860e8 100644
--- a/MyTracks/res/layout/about.xml
+++ b/MyTracks/res/layout/about.xml
@@ -58,6 +58,11 @@ limitations under the License.
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:orientation="vertical">
+
+
+
+
+
+ - Inactiu
+ - 1 min
+ - 2 min
+ - 5 min
+ - 10 min
+ - 15 min
+ - 30 min
+ - 60 min
+
+
+
+
+ - 1 m
+ - 2 m
+ - 3 m
+ - 4 m
+ - 5 m (recomanat)
+ - 10 m
+ - 100 m
+
+
+
+ - 3 ft
+ - 6 ft
+ - 10 ft
+ - 13 ft
+ - 16 ft (recomanat)
+ - 33 ft
+ - 328 ft
+
+
+
+
+ - 50 m
+ - 100 m
+ - 200 m (recomanat)
+ - 500 m
+ - 1000 m
+ - 5000 m
+
+
+
+ - 164 ft
+ - 328 ft
+ - 656 ft (recomanat)
+ - 1640 ft
+ - 0.6 miles
+ - 3.1 miles
+
+
+
+
+ - poca precisió (adaptable)
+ - gran precisió (adaptable)
+ - el millor (recomanat)
+ - 2 s
+ - 3 s
+ - 4 s
+ - 5 s
+ - 10 s
+ - 20 s
+ - 30 s
+ - 1 min
+ - 2 min
+ - 5 min
+ - 10 min
+ - 15 min
+ - 30 min
+
+
+
+
+ - 10 m (senyal GPS excel·lent)
+ - 20 m
+ - 50 m
+ - 100 m
+ - 200 m (recomanat)
+ - 500 m
+ - 1000 m
+ - 2000 m
+ - 5000 m (senyal GPS pobre)
+
+
+
+ - 33 ft (senyal GPS excel·lent)
+ - 66 ft
+ - 165 ft
+ - 328 ft
+ - 656 ft (recomanat)
+ - 1640 ft
+ - 0.6 miles
+ - 1.2 miles
+ - 3.1 miles (senyal GPS pobre)
+
+
+
+
+ - Sense divisió
+ - 100 km
+ - 50 km
+ - 25 km
+ - 10 km
+ - 5 km
+ - 1 km
+ - 60 min
+ - 30 min
+ - 15 min
+ - 10 min
+ - 5 min
+ - 2 min
+ - 1 min
+
+
+
+ - No splitting
+ - 100 mi
+ - 50 mi
+ - 25 mi
+ - 10 mi
+ - 5 mi
+ - 1 mi
+ - 60 min
+ - 30 min
+ - 15 min
+ - 10 min
+ - 5 min
+ - 2 min
+ - 1 min
+
+
+
+
+ - Mai
+ - 5 min
+ - 10 min
+ - 15 min
+ - Always
+
+
+
+
+ - desconegut
+ - tipus barrejats
+ - altre
+
+ - córrer
+ - córrer al carrer
+ - pista d\'atletisme
+ - córrer en ruta
+ - a peu
+ - caminar de presa
+ - senderisme
+ - ruta de senderisme
+ - senderisme fora de ruta
+ - escalada
+
+ - ciclisme
+ - bici al carrer
+ - bici de muntanya
+ - ruta de bici
+
+ - patinatge
+ - patinatge en línia
+ - skate
+
+ - snow shoeing
+ - skiing
+ - snow boarding
+ - sled
+ - ice sailing
+
+ - pesca
+ - rem
+ - caiac
+ - rem
+ - llanxa motora
+ - ferry
+ - RC boat
+ - surf
+ - windsurf
+ - kitesurf
+ - esquí aquàtic
+ - wakeboard
+
+ - avió
+ - hidroavió
+ - avió comercial
+ - helicòpter
+ - globus aerostàtic
+ - dirigible
+ - avió RC
+ - helicòpter RC
+ - salt d\'esquí
+ - vol sense motor
+ - ala delta
+ - parapent
+
+ - riding
+ - horse back riding
+ - donkey back riding
+
+ - conducció d\'automòbil
+ - conducció de bus
+ - tren
+ - moto
+ - dirtbike
+ - ATV
+
+ - landsailing
+ - frisbee
+ - ultimate frisbee
+
+
+
+ - ATM
+ - banc
+ - bar
+ - barbeque
+ - edifici
+ - parada de bús
+ - cafeteria
+ - acampada
+ - càmping
+ - església
+ - cafeteria
+ - botiga de conveniència
+ - aigua potable
+ - caiguda de roques
+ - ferry
+ - llar de foc
+ - estació de bombers
+ - pesca
+ - benzinera
+ - camp de golf
+ - botiga de queviures
+ - perruqueria
+ - port
+ - ruta de senderisme
+ - hotel
+ - casa
+ - informació
+ - intersecció
+ - bústia
+ - mercat
+ - punt de trobada
+ - opera
+ - parking
+ - oficina de correus
+ - sot
+ - comissaria dels mossos
+ - encreuament ferroviari
+ - restaurant
+ - compres
+ - vista
+ - magatzem
+ - piscina
+ - telèfon
+ - teatre
+ - serveis
+ - informació turística
+ - túnel
+ - arbre
+ - cascada
+ - font d\'aigua
+ - punt de referència
+
+
+
+ - Cap
+ - Zephyr Bluetooth
+
+
+
diff --git a/MyTracks/res/values-ca/strings.xml b/MyTracks/res/values-ca/strings.xml
new file mode 100644
index 000000000..1a001c3e2
--- /dev/null
+++ b/MyTracks/res/values-ca/strings.xml
@@ -0,0 +1,449 @@
+
+
+
+
+ Enregistra la ruta
+ Atura l\'enregistrament
+ Mode de satèl·lit
+ Mode de mapa
+ La meva ubicació
+ Rutes
+ Marcadors
+ Elimina totes les rutes
+ Paràmetres
+ Elevació
+ Seleccioneu la ruta que es visualitzarà
+ Ruta actual
+ Darrer segment
+ Estat del sensor
+ Estadístiques agregades
+
+
+ No s\'ha trobat cap gestor d\'ubicacions en aquest dispositiu.
+ No s\'ha trobat cap proveïdor d\'ubicació GPS en aquest dispositiu.
+ S\'ha produït un error quan s\'enviava la ruta a Google My Map. Proveu més tard.
+ S\'ha prdouït un error quan s\'enviava la ruta a Google Docs. Proveu més tard.
+ No es pot iniciar l\'enregistrament. Proveu de nou.
+ No es pot aturar l\'enregistrament. Proveu de nou.
+ No es pot llegir des del fitxer.
+ El fitxer no conté un format GPX vàlid.
+ S\'ha produït un error. Proveu de nou.
+ No existeix la ruta.
+ Aquest fitxer és massa gran (ha d\'ésser inferior a 500KB. S\'està cancel·lant.
+ No es pot inserir un marcador. No hi ha cap ubicació fixa disponible.
+
+
+ S\'està esperant per tenir una posició GPS fixa. Assegureu-vos que el cel és visible.
+ Ara s\'està enregistrant una ruta nova.
+ S\'ha aturat l\'enregistrament de la ruta.
+ S\'ha creat un Google My Map nou a maps.google.com.
+ 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.
+ S\'ha inserit un marcader de punt de ruta.
+ S\'ha inserit un marcador d\'estadístiques.
+
+
+ Espereu...
+ S\'està enviant la ruta a Google.
+ S\'està llegint la ruta...
+ S\'està preparant la ruta...
+ S\'està to Google Maps...
+ S\'està autenticant a Google Docs...
+ S\'està enviant la ruta a Google Docs...
+ S\'està enviant a Google Maps...
+ S\'està creant un My Map nou...
+ S\'està imporant una ruta GPS. Això pot trigar una estona.
+ S\'està escrivint a la targeta SD. Això pot trigar una estona.
+ S\'estan actualitzant les estadístiques de la ruta...
+
+
+ Esteu segur?
+ Tots els marcadors i rutes s\'eliminaran d\'aquest telèfon.
+ La ruta i els seus marcadors s\'eliminaran de forma permanent d\'aquest telèfon.
+ El marcador s\'eliminarà de forma permanent d\'aquest telèfon.
+
+
+ Creat per My Tracks a l\'Android.
+ Creat per <a href=\'http://mytracks.appspot.com\'>My Tracks</a> a l\'Android.
+ Mapa nou
+
+
+ Punts
+ Enregistrat
+ Tipus d\'activitat
+ Tipus de marcador
+ (Part %d)
+
+
+ S\'està enregistrant la ruta...
+ Ruta %d
+
+
+ No
+ Sí
+ D\'acord
+ Error
+ Fet
+ Punt de referència
+
+
+ (Inici)
+ (Fi)
+
+
+ Mostra
+ Envia a Google My Maps
+ Envia a un Google My Map nou
+ Envia a Google Docs
+ Envia a Google...
+ Elimina del telèfon...
+ Edita els detalls
+ Escriu a la targeta SD...
+ Anomena i desa com un fitxer GPX
+ Anomena i desa com un fitxer KML
+ Anomena i desa com un fitxer CSV
+ Anomena i desa com un fitxer TCX
+ Envia a Google My Maps
+ Elimina totes les rutes
+
+ Exporta totes les rutes
+ Exporta les finalitzades
+ Seleccioneu una ruta. Mantingueu el dit per veure més opcions.
+ Totes les rutes:
+ Aquesta ruta
+ Comparteix amb amics...
+ Un enllaç a Google My Map
+ Un fitxer GPX
+ Un fitxer KML
+ Un fitxer CSV
+ Un fitxer TCX
+ Neteja el mapa
+
+
+ Elimina tots els marcadors
+ Aquest marcador
+ Elimina
+ Edita els detalls
+ Mostra al mapa
+ Insereix un punt de referència
+ Insereix estadístiques
+ Seleccioneu un marcador. Mantingueu el dit per veure més opcions.
+
+
+ Detalls de la ruta
+ Nom de la ruta
+ Descripció de la ruta
+ Tipus d\'activitat (p.e. senderisme, bicicleta, còrrer, escalada...)
+
+
+ Detalls del marcador
+ Nom del marcador
+ Descripció del marcador
+ Tipus de marcador (e.g. edifici, sot, vista...)
+
+
+ Paràmetres de My Tracks
+ Visualització
+ Enregistrament
+ Compartició
+
+ No es troba disponible a la vostra versió de l\'Android
+
+ Freqüència d\'avís
+ Temps mínin entre dos avisos de veu
+
+ Distància mínima entre punts
+ La distància més petita entre dos punts enregistrats de la vostra posició
+
+ Distància màxima entre punts
+ Si dos punts estan més lluny d\'això, es dividirà la ruta
+
+ Temps mínim entre punts
+ Interval de temps mínim entre dos enregistraments de la vostra posició
+
+ Precisió míima
+ S\'aturarà l\'enregistrament si la vostra posició no és així de precisa com a mínim
+
+ Fes servir unitats mètriques
+ Totes les distàncies es mostraran en unitats mètriques
+ Totes les distàncies es mostraran en unitats imperials
+
+ Fes el mapa públic
+ Els mapes pujats seran visibles per tothom a maps.google.com
+ Els mapes pujats no es llistaran públicament a maps.google.com
+
+ Mostra la velocitat
+ Es mostrarà la velocitat
+ Es mostrarà el ritme de pas
+
+ Comparteix només l\'URL
+ Només s\'enviarà l\'URL del mapa quan es comparteixi
+ També s\'inclouran els continguts de la ruta quan es comparteixi
+
+ Freqüència de divisió
+ Temps o distància màxima a partir de la qual s\'insertarà un marcador de divisió nou
+
+ Temps d\'espera per reprende la ruta
+ Temps màxim en minuts a partir del qual l\'anterior ruta enregistrada no es reprendrà en actualitzar el telèfon
+
+ Data i hora com a nom de la ruta
+ El nom per defecte de la ruta serà la data i hora en que s\'ha iniciat.
+ Els noms de les rutes seran números que s\'incrementaran per a cada nova ruta.
+
+ Ritme de pas
+
+ Suportat només en Android 2.0 i superior
+ Tipus de sensor
+ El tipus de sensor al qual es connecta.
+ Identificador del sensor
+ L\'identificador enviat als sensors.
+ L\'identificador ha de ser un valor sencer.
+ Sensor bluetooth
+ El sensor bluetooth al qual es connecta.
+ Pantalla d\'estadístiques modulars
+ La pantalla d\'estadístiques es divideix en mòduls diferents.
+ Mostra totes les estadístiques en una sola pantalla.
+
+ Còpia de seguretat (BETA)
+ Còpia de seguretat a la targeta SD
+ Fes la còpia ara
+ Escriu una còpia completa de totes les rutes i els paràmetres a la targeta SD
+ Restaura ara
+ Restaura totes les rutes i els paràmetres des de la targeta SD.
+ No es poden gestionar les còpies de seguretat mentre s\'enregistra una ruta.
+
+
+ La restauració d\'una còpia de seguretat des de la targeta SD sobreescriurà totes les rutes i paràmetres. Esteu segur?
+ Seleccioneu la còpia que es restaurarà
+ No s\'ha trobat cap còpia de seguretat.
+ S\'estan escrivint totes les rutes i els paràmetres a la targeta SD. Això pot trigar una estona...
+ S\'estan llegint totes les rutes i els paràmetres de la targeta SD. Això pot trigar una estona...
+
+
+ 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:
+
+ %s
+ M\'agradaria compartir una ruta amb tu
+ Crec que estaràs interessat en aquesta ruta.
+
+ Per verue aquesta ruta fes servir Google Earth o altre programari de mapes.
+
+
+ Públic
+ No llistat
+ Encara no teniu cap mapa.
+
+
+
+ Average Moving Speed
+ Average Moving Pace
+
+
+ Velocitat mitjana
+ Ritme de pas mitjà
+
+ Gràfic d\'elevació
+ Guany d\'elevació
+ Elevació
+ Latitud
+ Longitud
+
+
+ Elevació mínima
+ Grau mínim
+ Ritme de pas mínima
+
+
+ Elevació màxima
+ Grau màxim
+ Velocitat màxima
+
+ S\'està carregant la ruta...
+
+
+ Configuració del gràfic
+
+ Eix X
+ Per distància
+ Per temps
+
+
+ Eix Y
+
+
+
+ hora
+ hores
+ minut
+ minuts
+ segon
+ segons
+
+
+ kilòmetres
+ milles
+
+
+ kilòmetres per hora
+ per kilòmetre
+ milles per hora
+ per milla
+
+
+ Temps en moviment
+ Temps total
+ Temps
+ La targeta SD no està disponible
+ Velocitat
+ Heu d\'iniciar la marxa.
+ Estadístiques
+ Distància total
+ Distància
+ Energia
+ Cadència
+ Freqüència cardíaca
+ Freqüència cardíaca màxima
+ S\'està esperant...
+
+
+ Acord de llicencia d\'usuari final
+ Accepta
+ Rebutja
+
+
+ Quant a
+ Ajuda
+ Equip:
+ Versió:
+ Assistència
+ Assistència:
+
+
+ Envia a...
+ Envia ara
+ Crea un mapa nou
+ Seleccioneu un mapa existent
+ Envia les estadístiques de la ruta
+ Envia les estadístiques i els punts
+
+
+ No s\'ha trobat cap compte
+ No s\'ha trobat cap compte on pujar les rutes.
+ Trieu un compte
+
+
+ No s\'ha trobat cap dispositiu d\'emmagatzematge extern. Assegureu-vos que la targeta SD està inserida i que el telèfon no està connectat a un ordinador.
+ No es pot escriure a un fitxer.
+ No es pot llegir des del fitxer.
+ No es pot crear el directori a la targeta SD.
+ S\'ha finalitzat l\'escriptura a la targeta SD.
+ S\'ha finalitzat la lectura a la targeta SD.
+
+
+ Comprovació d\'actualitzacions
+ Actualitza ara
+ Continua sense actualitzar
+ La vostra versió de %1$s podria no funcionar correctament a partir d\'ara i no està suportada. Actualitzeu per continuar fent servir %1$s.
+ La vostra versió de %1$s podria no funcionar correctament a partir d\'ara. Heu d\'actualitzar-la ara o continuar fent-la servir pel vostre compte. Algunes funcionalitats com la sincronització podrien no funcionar fins que no actualitzeu.
+ Hi ha una nova versió disponible de %1$s. Podeu continuar fent servir la vostra versió sense cap problema, però no aconseguireu les darreres funcionalitats fins que no actualitzeu.
+
+
+ Benvingut
+ D\'acord
+ Quant a My Tracks
+
+
+ Sense distància My Tracks
+
+ My Tracks %1$s %2$.2f %3$s en %4$s at %5$s %6$s
+
+
+ Avís de les unitats de mesura
+ Les unitats són mètriques per defecte. Voleu canviar aquest paràmetre?
+
+
+ Rati d\'error: %1$d
+ Relació senyal/soroll: %1$d
+ Ec/lo: %1$.1f dB
+ Intensistat GSM: %1$d
+ Intensitat CDMA: %1$d dBm
+ Intensitat EVDO: %1$d dBm
+
+
+ Opcions del Bluetooth
+ Associació de dispositius
+ Obriu els paràmetres de bluetooth de l\'Android per associar un dispositiu
+ bluetoothSensorsEnabled
+ Opcions del sensor
+
+
+ Connectat a %s
+ Connexió del sensor bluetooth...
+ El sensor bluetooth no està connectat
+
+
+ Dades del sensor de temps
+ S\'ha habilitat el mesurador de potència
+ S\'ha habilitat el monitor de la freqüència cardíaca
+ Cap gestor de sensors
+ Habilitat
+ S\'està connectant
+ S\'ha connectat
+ S\'ha desconnectat
+ Cap servei de sensor
+ S\'està cercant
+ S\'està enviant
+
+
diff --git a/MyTracks/res/values-pl/strings.xml b/MyTracks/res/values-pl/strings.xml
index 2d597a9e2..017176bb3 100644
--- a/MyTracks/res/values-pl/strings.xml
+++ b/MyTracks/res/values-pl/strings.xml
@@ -361,6 +361,8 @@ limitations under the License.
Wersja:
Wsparcie
Wsparcie:
+
+ Bartłomiej Niechwiej
Wyślij do...
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index b837dcc82..c2d8886e9 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -388,6 +388,7 @@ limitations under the License.
mytracks@googlegroups.com
mytracks.appspot.com
Copyright 2009 Google Inc.
+ Bartlomiej Niechwiej
Dylan Casey
Leif Hendrik Wilden
Rodrigo Damazio Bovendorp
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
index c7ade2cdc..ddf9306eb 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
@@ -25,7 +25,6 @@ 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.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
@@ -94,9 +93,6 @@ import org.xml.sax.SAXException;
public class MyTracks extends TabActivity implements OnTouchListener,
OnSharedPreferenceChangeListener, ProgressIndicator {
- private static final String WAYPOINT_ICON_URL =
- "http://maps.google.com/mapfiles/ms/micons/blue-pushpin.png";
-
/**
* Singleton instance
*/
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
index 56c70db13..2dd891ac2 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
@@ -21,6 +21,7 @@ import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
+import java.util.Iterator;
import java.util.List;
/**
@@ -309,6 +310,87 @@ public interface MyTracksProviderUtils {
* @return a new waypoint object
*/
Waypoint createWaypoint(Cursor cursor);
+
+ /**
+ * A lightweight wrapper around the original {@link Cursor} with a method to clean up.
+ */
+ interface LocationIterator extends Iterator {
+ /**
+ * Returns ID of the most recently retrieved track point through a call to {@link #next()}.
+ *
+ * @return the ID of the most recent track point ID.
+ */
+ long getLocationId();
+
+ /**
+ * Should be called in case the underlying iterator hasn't reached the last record.
+ */
+ void close();
+ }
+
+ /**
+ * A factory for creating new {@class Location}s.
+ */
+ interface LocationFactory {
+ /**
+ * Creates a new {@link Location} object to be populated from the underlying database record.
+ * It's up to the implementing class to decide whether to create a new instance or reuse
+ * existing to optimize for speed.
+ *
+ * @return a {@link Location} to be populated from the database.
+ */
+ Location createLocation();
+ }
+
+ /**
+ * The default {@class Location}s factory, which creates a new location of 'gps' type.
+ */
+ LocationFactory DEFAULT_LOCATION_FACTORY = new LocationFactory() {
+ @Override
+ public Location createLocation() {
+ return new Location("gps");
+ }
+ };
+
+ /**
+ * Creates a new read-only iterator over all track points for the given track. It provides
+ * a lightweight way of iterating over long tracks without failing due to the underlying cursor
+ * limitations. Since it's a read-only iterator, {@link Iterator#remove()} always throws
+ * {@class UnsupportedOperationException}.
+ *
+ * Each call to {@link LocationIterator#next()} may advance to the next DB record, and if so,
+ * the iterator calls {@link LocationFactory#createLocation()} and populates it with information
+ * retrieved from the record.
+ *
+ * When done with iteration, you must call {@link LocationIterator#close()} to make sure that all
+ * resources are properly deallocated.
+ *
+ * Example use:
+ *
+ * ...
+ * LocationIterator it = providerUtils.getLocationIterator(
+ * 1, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ * try {
+ * for (Location loc : it) {
+ * ... // Do something useful with the location.
+ * }
+ * } finally {
+ * it.close();
+ * }
+ * ...
+ *
+ *
+ * @param trackId the ID of a track to retrieve locations for.
+ * @param startTrackPointId the ID of the first track point to load, or -1 to start from
+ * the first point.
+ * @param descending if true the results will be returned in descending ID
+ * order (latest location first).
+ * @param locationFactory the factory for creating new locations.
+ *
+ * @return the read-only iterator over the given track's points.
+ */
+ LocationIterator getLocationIterator(long trackId, long startTrackPointId, boolean descending,
+ LocationFactory locationFactory);
/**
* A factory which can produce instances of {@link MyTracksProviderUtils},
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 a8ac9aa4a..33bfb6eef 100644
--- a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
@@ -15,7 +15,7 @@
*/
package com.google.android.apps.mytracks.content;
-import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.*;
+import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -29,6 +29,7 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
+import java.util.NoSuchElementException;
/**
* Helper class providing easy access to locations and tracks in the
@@ -39,6 +40,8 @@ import java.util.List;
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private final ContentResolver contentResolver;
+
+ private int defaultCursorBatchSize = 2000;
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
@@ -186,55 +189,78 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
fillLocation(cursor, location);
return location;
}
+
+ /**
+ * A cache of track column indices.
+ */
+ private static class CachedTrackColumnIndices {
+ public final int idxId;
+ public final int idxLatitude;
+ public final int idxLongitude;
+ public final int idxAltitude;
+ public final int idxTime;
+ public final int idxBearing;
+ public final int idxAccuracy;
+ public final int idxSpeed;
+ public final int idxSensor;
- @Override
- public void fillLocation(Cursor cursor, Location location) {
+ public CachedTrackColumnIndices(Cursor cursor) {
+ idxId = cursor.getColumnIndex(TrackPointsColumns._ID);
+ idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
+ idxLongitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
+ idxAltitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
+ idxTime = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
+ idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
+ idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
+ idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
+ idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
+ }
+ }
+
+ private void fillLocation(Cursor cursor, CachedTrackColumnIndices columnIndices,
+ Location location) {
location.reset();
-
- int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
- int idxLongitude =
- cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
- int idxAltitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
- int idxTime = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
- int idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
- int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
- int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
- int idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
-
- if (!cursor.isNull(idxLatitude)) {
- location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
+
+ if (!cursor.isNull(columnIndices.idxLatitude)) {
+ location.setLatitude(1. * cursor.getInt(columnIndices.idxLatitude) / 1E6);
}
- if (!cursor.isNull(idxLongitude)) {
- location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6);
+ if (!cursor.isNull(columnIndices.idxLongitude)) {
+ location.setLongitude(1. * cursor.getInt(columnIndices.idxLongitude) / 1E6);
}
- if (!cursor.isNull(idxAltitude)) {
- location.setAltitude(cursor.getFloat(idxAltitude));
+ if (!cursor.isNull(columnIndices.idxAltitude)) {
+ location.setAltitude(cursor.getFloat(columnIndices.idxAltitude));
}
- if (!cursor.isNull(idxTime)) {
- location.setTime(cursor.getLong(idxTime));
+ if (!cursor.isNull(columnIndices.idxTime)) {
+ location.setTime(cursor.getLong(columnIndices.idxTime));
}
- if (!cursor.isNull(idxBearing)) {
- location.setBearing(cursor.getFloat(idxBearing));
+ if (!cursor.isNull(columnIndices.idxBearing)) {
+ location.setBearing(cursor.getFloat(columnIndices.idxBearing));
}
- if (!cursor.isNull(idxSpeed)) {
- location.setSpeed(cursor.getFloat(idxSpeed));
+ if (!cursor.isNull(columnIndices.idxSpeed)) {
+ location.setSpeed(cursor.getFloat(columnIndices.idxSpeed));
}
- if (!cursor.isNull(idxAccuracy)) {
- location.setAccuracy(cursor.getFloat(idxAccuracy));
+ if (!cursor.isNull(columnIndices.idxAccuracy)) {
+ location.setAccuracy(cursor.getFloat(columnIndices.idxAccuracy));
}
if (location instanceof MyTracksLocation &&
- !cursor.isNull(idxSensor)) {
+ !cursor.isNull(columnIndices.idxSensor)) {
MyTracksLocation mtLocation = (MyTracksLocation) location;
// TODO get the right buffer.
Sensor.SensorDataSet sensorData;
try {
- sensorData = Sensor.SensorDataSet.parseFrom(cursor.getBlob(idxSensor));
+ sensorData = Sensor.SensorDataSet.parseFrom(cursor.getBlob(columnIndices.idxSensor));
mtLocation.setSensorData(sensorData);
} catch (InvalidProtocolBufferException e) {
Log.w(TAG, "Failed to parse sensor data.", e);
}
}
}
+
+ @Override
+ public void fillLocation(Cursor cursor, Location location) {
+ CachedTrackColumnIndices columnIndicies = new CachedTrackColumnIndices(cursor);
+ fillLocation(cursor, columnIndicies, location);
+ }
@Override
public Track createTrack(Cursor cursor) {
@@ -792,13 +818,12 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
public Cursor getLocationsCursor(long trackId, long minTrackPointId,
int maxLocations, boolean descending) {
String selection;
- if (minTrackPointId > 0) {
- selection = String.format("%s=%d AND %s>=%d",
- TrackPointsColumns.TRACKID, trackId,
- TrackPointsColumns._ID, minTrackPointId);
+ if (minTrackPointId >= 0) {
+ selection = String.format("%s=%d AND %s%s%d",
+ TrackPointsColumns.TRACKID, trackId, TrackPointsColumns._ID,
+ descending ? "<=" : ">=", minTrackPointId);
} else {
- selection = String.format("%s=%d",
- TrackPointsColumns.TRACKID, trackId);
+ selection = String.format("%s=%d", TrackPointsColumns.TRACKID, trackId);
}
String sortOrder = "_id " + (descending ? "DESC" : "ASC");
@@ -806,8 +831,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxLocations;
}
- return contentResolver.query(
- TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
+ return contentResolver.query(TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@Override
@@ -951,4 +975,85 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
contentResolver.update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
+
+ @Override
+ public LocationIterator getLocationIterator(final long trackId, final long startTrackPointId,
+ final boolean descending, final LocationFactory locationFactory) {
+ if (locationFactory == null) {
+ throw new IllegalArgumentException("Expecting non-null locationFactory");
+ }
+ return new LocationIterator() {
+ private long lastTrackPointId = startTrackPointId;
+ private Cursor cursor = getCursor(startTrackPointId);
+ private final CachedTrackColumnIndices columnIndices = cursor != null ?
+ new CachedTrackColumnIndices(cursor) : null;
+
+ private Cursor getCursor(long trackPointId) {
+ return getLocationsCursor(trackId, trackPointId, defaultCursorBatchSize, descending);
+ }
+
+ private boolean advanceCursorToNextBatch() {
+ long pointId = lastTrackPointId + (descending ? -1 : 1);
+ Log.d(TAG, "Advancing cursor point ID: " + pointId);
+ cursor.close();
+ cursor = getCursor(pointId);
+ return cursor != null;
+ }
+
+ @Override
+ public long getLocationId() {
+ return lastTrackPointId;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (cursor == null) {
+ return false;
+ }
+ if (cursor.isAfterLast()) {
+ return false;
+ }
+ if (cursor.isLast()) {
+ // If the current batch size was less that max, we can safely return, otherwise
+ // we need to advance to the next batch.
+ return cursor.getCount() == defaultCursorBatchSize &&
+ advanceCursorToNextBatch() && !cursor.isAfterLast();
+ }
+
+ return true;
+ }
+
+ @Override
+ public Location next() {
+ if (cursor == null ||
+ !(cursor.moveToNext() || advanceCursorToNextBatch() || cursor.moveToNext())) {
+ throw new NoSuchElementException();
+ }
+
+ lastTrackPointId = cursor.getLong(columnIndices.idxId);
+ Location location = locationFactory.createLocation();
+ fillLocation(cursor, columnIndices, location);
+
+ return location;
+ }
+
+ @Override
+ public void close() {
+ if (cursor != null) {
+ cursor.close();
+ cursor = null;
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ // @VisibleForTesting
+ void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
+ this.defaultCursorBatchSize = defaultCursorBatchSize;
+ }
}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImplTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImplTest.java
new file mode 100644
index 000000000..9107221a9
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImplTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks.content;
+
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
+import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
+
+import android.content.Context;
+import android.location.Location;
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContentResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A unit test for {@link MyTracksProviderUtilsImpl}.
+ *
+ * @author Bartlomiej Niechwiej
+ */
+public class MyTracksProviderUtilsImplTest extends AndroidTestCase {
+ private Context context;
+ private MyTracksProviderUtils providerUtils;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ MockContentResolver mockContentResolver = new MockContentResolver();
+ RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
+ getContext(), getContext(), "test.");
+ context = new MockContext(mockContentResolver, targetContext);
+ MyTracksProvider provider = new MyTracksProvider();
+ provider.attachInfo(context, null);
+ mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
+ setContext(context);
+
+ providerUtils = MyTracksProviderUtils.Factory.get(context);
+ }
+
+ public void testLocationIterator_noPoints() {
+ testIterator(1, 0, 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ public void testLocationIterator_customFactory() {
+ final Location location = new Location("test_location");
+ final AtomicInteger counter = new AtomicInteger();
+ testIterator(1, 15, 4, false, new LocationFactory() {
+ @Override
+ public Location createLocation() {
+ counter.incrementAndGet();
+ return location;
+ }
+ });
+ // Make sure we were called exactly as many times as we had track points.
+ assertEquals(15, counter.get());
+ }
+
+ public void testLocationIterator_nullFactory() {
+ try {
+ testIterator(1, 15, 4, false, null);
+ fail("Expecting IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ public void testLocationIterator_noBatchAscending() {
+ testIterator(1, 50, 100, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ testIterator(2, 50, 50, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ public void testLocationIterator_noBatchDescending() {
+ testIterator(1, 50, 100, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ testIterator(2, 50, 50, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ public void testLocationIterator_batchAscending() {
+ testIterator(1, 50, 11, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ testIterator(2, 50, 25, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ public void testLocationIterator_batchDescending() {
+ testIterator(1, 50, 11, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ testIterator(2, 50, 25, true, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ public void testLocationIterator_largeTrack() {
+ testIterator(1, 20000, 2000, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ }
+
+ private List testIterator(long trackId, int numPoints, int batchSize,
+ boolean descending, LocationFactory locationFactory) {
+ long lastPointId = initializeTrack(trackId, numPoints);
+ ((MyTracksProviderUtilsImpl) providerUtils).setDefaultCursorBatchSize(batchSize);
+ List locations = new ArrayList(numPoints);
+ LocationIterator it = providerUtils.getLocationIterator(trackId, -1, descending, locationFactory);
+ try {
+ while (it.hasNext()) {
+ Location loc = it.next();
+ assertNotNull(loc);
+ locations.add(loc);
+ // Make sure the IDs are returned in the right order.
+ assertEquals(descending ? lastPointId - locations.size() + 1
+ : lastPointId - numPoints + locations.size(), it.getLocationId());
+ }
+ assertEquals(numPoints, locations.size());
+ } finally {
+ it.close();
+ }
+ return locations;
+ }
+
+ private long initializeTrack(long id, int numPoints) {
+ Track track = new Track();
+ track.setId(id);
+ track.setName("Test: " + id);
+ track.setNumberOfPoints(numPoints);
+ providerUtils.insertTrack(track);
+ track = providerUtils.getTrack(id);
+ assertNotNull(track);
+
+ Location[] locations = new Location[numPoints];
+ for (int i = 0; i < numPoints; ++i) {
+ Location loc = new Location("test");
+ loc.setLatitude(37.0 + (double) i / 10000.0);
+ loc.setLongitude(57.0 - (double) i / 10000.0);
+ loc.setAccuracy((float) i / 100.0f);
+ loc.setAltitude(i * 2.5);
+ locations[i] = loc;
+ }
+ providerUtils.bulkInsertTrackPoints(locations, numPoints, id);
+
+ // Load all inserted locations.
+ long lastPointId = -1;
+ int counter = 0;
+ LocationIterator it = providerUtils.getLocationIterator(id, -1, false,
+ MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
+ try {
+ while (it.hasNext()) {
+ it.next();
+ lastPointId = it.getLocationId();
+ counter++;
+ }
+ } finally {
+ it.close();
+ }
+
+ assertTrue(numPoints == 0 || lastPointId > 0);
+ assertEquals(numPoints, track.getNumberOfPoints());
+ assertEquals(numPoints, counter);
+
+ return lastPointId;
+ }
+}
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
index 3391e3957..9f3e0e186 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/TrackRecordingServiceTest.java
@@ -78,9 +78,11 @@ public class TrackRecordingServiceTest
}
/**
- * A context wrapper with the user provided {@link ContentResolver}.
+ * A context wrapper with the user provided {@link ContentResolver}.
+ *
+ * TODO: Move to test utils package.
*/
- private static class MockContext extends ContextWrapper {
+ public static class MockContext extends ContextWrapper {
private final ContentResolver contentResolver;
public MockContext(ContentResolver contentResolver, Context base) {