diff --git a/.hgignore b/.hgignore
index 1c3f1f5a7..ffa8823b5 100644
--- a/.hgignore
+++ b/.hgignore
@@ -7,3 +7,5 @@ syntax:glob
MyTracksTest/annotations.log
**/*.pyc
**/*.pyo
+syntax: glob
+MyTracksTest/hs_err_pid*.log
diff --git a/MyTracks/.classpath b/MyTracks/.classpath
index 4a0943047..4a148f304 100644
--- a/MyTracks/.classpath
+++ b/MyTracks/.classpath
@@ -3,8 +3,16 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/MyTracks/.project b/MyTracks/.project
index 5a87d6231..4ff12bb43 100644
--- a/MyTracks/.project
+++ b/MyTracks/.project
@@ -1,41 +1,41 @@
-
-
- MyTracks
-
-
- MyTracksLib
-
-
-
- com.android.ide.eclipse.adt.ResourceManagerBuilder
-
-
-
-
- com.android.ide.eclipse.adt.PreCompilerBuilder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- com.android.ide.eclipse.adt.ApkBuilder
-
-
-
-
-
- com.android.ide.eclipse.adt.AndroidNature
- org.eclipse.jdt.core.javanature
-
-
-
- MyTracksLib_src
- 2
- _android_MyTracksLib_e2802071/src
-
-
-
+
+
+ MyTracks
+
+
+ MyTracksLib
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
+
+ MyTracksLib_src
+ 2
+ _android_MyTracksLib_e2802071/src
+
+
+
diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index d085d37e9..f3752ef43 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -17,10 +17,11 @@ limitations under the License.
+ android:versionCode="33"
+ android:versionName="1.1.10">
@@ -177,6 +178,9 @@ limitations under the License.
android:exported="true"
android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
+
+
@@ -188,7 +192,6 @@ limitations under the License.
-
@@ -230,7 +233,7 @@ limitations under the License.
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/notification_permission_label"
android:description="@string/notification_permission_description"
- android:protectionLevel="normal" />
+ android:protectionLevel="dangerous" />
+
+
+
-
+
diff --git a/MyTracks/default.properties b/MyTracks/default.properties
index ee3f68e34..75e62f9cb 100644
--- a/MyTracks/default.properties
+++ b/MyTracks/default.properties
@@ -13,7 +13,5 @@ split.density=false
target=Google Inc.:Google APIs:13
android.library.reference.1=../MyTracksLib/
-# ProGuard is turned off while we investigate the removal of code that
-# isn't actually dead from Fusion Tables. See issue 303 for an example of
-# the result of that removal.
-#proguard.config=proguard.cfg
+# Enable proguard.
+proguard.config=proguard.cfg
diff --git a/MyTracks/libs/README.txt b/MyTracks/libs/README.txt
index 62e619f85..0df5b07bb 100644
--- a/MyTracks/libs/README.txt
+++ b/MyTracks/libs/README.txt
@@ -3,12 +3,20 @@ own licenses, which allow their usage for the My Tracks application. Any other
applications that wish to use these files should consult the appropriate
licenses or the JAR's authors.
-References are provided to the libraries:
+google-api-client/
+- see http://code.google.com/p/google-api-java-client/.
+- contains Google API client library, google-api-client-1.5.0-beta.jar, and its
+ dependencies.
+- google-api-client-1.5.0-beta.jar depends on protobuf-java-2.2.0.jar, however,
+ MyTracksLib already includes protobuf-java.2.3.0-lite.jar. Thus using the
+ newer version, protobuf-java.2.3.0-lite.jar.
+- google-api-client-1.5.0-beta.jar also depends on xpp3-1.1.4c.jar, however,
+ some of its classes are already included in the android framework, thus not
+ including xpp3-1.1.4c.jar.
-google-api-client-*.jar (GData client):
-http://code.google.com/p/google-api-java-client/
+google-common.jar
-google-common.jar,
-googleloginclient-helper.jar,
-libmymaps.jar (Google My Maps upload library):
-Licensed exclusively for use in My Tracks and derived Android applications.
+googleloginclient-helper.jar
+
+libGoogleAnalytics.jar
+- see http://code.google.com/mobile/analytics/docs/.
diff --git a/MyTracks/libs/google-api-client-1.2.2-alpha.jar b/MyTracks/libs/google-api-client-1.2.2-alpha.jar
deleted file mode 100644
index 7194648fd..000000000
Binary files a/MyTracks/libs/google-api-client-1.2.2-alpha.jar and /dev/null differ
diff --git a/MyTracks/libs/google-api-client/commons-codec-1.3.jar b/MyTracks/libs/google-api-client/commons-codec-1.3.jar
new file mode 100644
index 000000000..957b6752a
Binary files /dev/null and b/MyTracks/libs/google-api-client/commons-codec-1.3.jar differ
diff --git a/MyTracks/libs/google-api-client/commons-logging-1.1.1.jar b/MyTracks/libs/google-api-client/commons-logging-1.1.1.jar
new file mode 100644
index 000000000..1deef144c
Binary files /dev/null and b/MyTracks/libs/google-api-client/commons-logging-1.1.1.jar differ
diff --git a/MyTracks/libs/google-api-client/google-api-client-1.5.0-beta.jar b/MyTracks/libs/google-api-client/google-api-client-1.5.0-beta.jar
new file mode 100644
index 000000000..41f2e578e
Binary files /dev/null and b/MyTracks/libs/google-api-client/google-api-client-1.5.0-beta.jar differ
diff --git a/MyTracks/libs/google-api-client/google-http-client-1.5.0-beta.jar b/MyTracks/libs/google-api-client/google-http-client-1.5.0-beta.jar
new file mode 100644
index 000000000..b5e610ddf
Binary files /dev/null and b/MyTracks/libs/google-api-client/google-http-client-1.5.0-beta.jar differ
diff --git a/MyTracks/libs/google-api-client/google-oauth-client-1.5.0-beta.jar b/MyTracks/libs/google-api-client/google-oauth-client-1.5.0-beta.jar
new file mode 100644
index 000000000..af73e9aa4
Binary files /dev/null and b/MyTracks/libs/google-api-client/google-oauth-client-1.5.0-beta.jar differ
diff --git a/MyTracks/libs/google-api-client/gson-1.6.jar b/MyTracks/libs/google-api-client/gson-1.6.jar
new file mode 100644
index 000000000..40648939f
Binary files /dev/null and b/MyTracks/libs/google-api-client/gson-1.6.jar differ
diff --git a/MyTracks/libs/google-api-client/guava-r09.jar b/MyTracks/libs/google-api-client/guava-r09.jar
new file mode 100644
index 000000000..30dbc5692
Binary files /dev/null and b/MyTracks/libs/google-api-client/guava-r09.jar differ
diff --git a/MyTracks/libs/google-api-client/jackson-core-asl-1.6.7.jar b/MyTracks/libs/google-api-client/jackson-core-asl-1.6.7.jar
new file mode 100644
index 000000000..7c1207210
Binary files /dev/null and b/MyTracks/libs/google-api-client/jackson-core-asl-1.6.7.jar differ
diff --git a/MyTracks/libs/google-api-client/jsr305-1.3.9.jar b/MyTracks/libs/google-api-client/jsr305-1.3.9.jar
new file mode 100644
index 000000000..a9afc6619
Binary files /dev/null and b/MyTracks/libs/google-api-client/jsr305-1.3.9.jar differ
diff --git a/MyTracks/proguard.cfg b/MyTracks/proguard.cfg
index 8a6087a72..8a6a82b85 100644
--- a/MyTracks/proguard.cfg
+++ b/MyTracks/proguard.cfg
@@ -14,6 +14,10 @@
-dontwarn com.google.android.gsf.**
-dontwarn org.codehaus.**
-dontwarn com.google.appengine.**
+-dontwarn org.apache.avalon.framework.logger.**
+-dontwarn org.apache.log.**
+-dontwarn org.apache.log4j.**
+-dontwarn javax.servlet.**
# Make our stack traces useful
# Line numbers will be correct, file names will be replaced by "MT" since the
@@ -51,3 +55,21 @@
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
+
+# Needed by google-api-client to keep generic types and @Key annotations accessed via reflection
+
+-keepclassmembers class * {
+ @com.google.api.client.util.Key ;
+}
+
+-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
+
+# Needed by Guava
+
+-dontwarn sun.misc.Unsafe
+
+# Keep services needed by ANT+ protocol
+-keep public class com.dsi.ant.IAnt_6
+-keep public class com.dsi.ant.IAnt
+-keep public class com.dsi.ant.IServiceSettings
+
diff --git a/MyTracks/res/layout/mytracks_detail.xml b/MyTracks/res/layout/mytracks_detail.xml
index d1a7fafa2..8312000f2 100644
--- a/MyTracks/res/layout/mytracks_detail.xml
+++ b/MyTracks/res/layout/mytracks_detail.xml
@@ -40,7 +40,9 @@ limitations under the License.
android:singleLine="true"
android:autoText="true"
android:capitalize="words"
- android:selectAllOnFocus="true"/>
+ android:selectAllOnFocus="true"
+ android:hint="@string/trackdetails_name_hint"
+ android:imeOptions="actionNext"/>
+ android:singleLine="true"
+ android:hint="@string/trackdetails_category_hint"
+ android:imeOptions="actionNext"/>
@@ -61,7 +65,9 @@ limitations under the License.
android:selectAllOnFocus="true"
android:maxLines="50"
android:gravity="top"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ android:hint="@string/trackdetails_description_hint"
+ android:imeOptions="actionDone"/>
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:orientation="horizontal"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip"
+ android:paddingTop="4dip">
+
+
-
+ style="@style/DialogButton"/>
+ style="@style/DialogButton"/>
+
+
diff --git a/MyTracks/res/layout/mytracks_waypoint_details.xml b/MyTracks/res/layout/mytracks_waypoint_details.xml
index fdfa87b4e..b62360c59 100644
--- a/MyTracks/res/layout/mytracks_waypoint_details.xml
+++ b/MyTracks/res/layout/mytracks_waypoint_details.xml
@@ -50,7 +50,9 @@ limitations under the License.
android:singleLine="true"
android:autoText="true"
android:capitalize="words"
- android:selectAllOnFocus="true"/>
+ android:selectAllOnFocus="true"
+ android:hint="@string/waypointdetails_name_hint"
+ android:imeOptions="actionNext"/>
+ android:singleLine="true"
+ android:hint="@string/waypointdetails_category_hint"
+ android:imeOptions="actionNext"/>
@@ -79,7 +83,9 @@ limitations under the License.
android:minLines="5"
android:maxLines="50"
android:gravity="top"
- android:layout_weight="1"/>
+ android:layout_weight="1"
+ android:hint="@string/waypointdetails_description_hint"
+ android:imeOptions="actionDone"/>
Detalls de la ruta
Nom de la ruta
+ Nom de la ruta
Descripció de la ruta
+ Descripció de la ruta
Tipus d\'activitat (p.e. senderisme, bicicleta, còrrer, escalada...)
+ Tipus d\'activitat
Detalls del marcador
Nom del marcador
+ Nom del marcador
Descripció del marcador
+ Descripció del marcador
Tipus de marcador (e.g. edifici, sot, vista...)
+ Tipus de marcador
Paràmetres de My Tracks
diff --git a/MyTracks/res/values-de/strings.xml b/MyTracks/res/values-de/strings.xml
index 40ca54239..4d8e28ac8 100644
--- a/MyTracks/res/values-de/strings.xml
+++ b/MyTracks/res/values-de/strings.xml
@@ -154,14 +154,20 @@ limitations under the License.
Trackdetails
Name
+ Name
Beschreibung
+ Beschreibung
Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…)
+ Aktivität
Wegpunktdetails
Wegpunktname
+ Wegpunktname
Wegpunktbeschreibung
+ Wegpunktbeschreibung
Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…)
+ Art des Wegpunktes
Einstellungen für My Tracks
diff --git a/MyTracks/res/values-es/strings.xml b/MyTracks/res/values-es/strings.xml
index fdb13e168..2bcc7e37c 100644
--- a/MyTracks/res/values-es/strings.xml
+++ b/MyTracks/res/values-es/strings.xml
@@ -138,14 +138,20 @@ limitations under the License.
Detalles del recorrido
Nombre del recorrido
+ Nombre del recorrido
Descripción del recorrido
+ Descripción del recorrido
Tipo de actividad (por ejemplo, ciclismo, alpinismo...)
+ Tipo de actividad
Detalles de señal
Nombre de señal
+ Nombre de señal
Descripción de señal
+ Descripción de señal
Tipo de señal (por ejemplo, edificio, bache, atracción...)
+ Tipo de señal
Configuraciones de My Tracks
diff --git a/MyTracks/res/values-fi/strings.xml b/MyTracks/res/values-fi/strings.xml
index 87d5a61b9..07a465c0d 100644
--- a/MyTracks/res/values-fi/strings.xml
+++ b/MyTracks/res/values-fi/strings.xml
@@ -183,14 +183,20 @@ limitations under the License.
Reitin tiedot
Reitin nimi
+ Reitin nimi
Reitin kuvaus
+ Reitin kuvaus
Liikunnan muoto (esim. hölkkä, pyöräily, juoksu...)
+ Liikunnan muoto
Merkinnän tiedot
Merkinnän nimi
+ Merkinnän nimi
Merkinnän kuvaus
+ Merkinnän kuvaus
Merkinnän tyyppi (esim. rakennus, kuoppa, nähtävyys...)
+ Merkinnän tyyppi
My Tracks Asetukset
diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml
index 49cc6516d..3a533a7f5 100644
--- a/MyTracks/res/values-fr/strings.xml
+++ b/MyTracks/res/values-fr/strings.xml
@@ -144,14 +144,20 @@ limitations under the License.
Détails parcours
Nom du parcours
+ Nom du parcours
Description du parcours
+ Description du parcours
Type d\'activité (randonnée, cyclisme, jogging, escalade...)
+ Type d\'activité
Details balise
Nom de la balise
+ Nom de la balise
Description balise
+ Description balise
Nom de balise (par ex.: immeuble, boutique, point de vue...)
+ Nom de balise
Paramètres My Tracks
diff --git a/MyTracks/res/values-it/strings.xml b/MyTracks/res/values-it/strings.xml
index a3419cafd..5a5c59911 100644
--- a/MyTracks/res/values-it/strings.xml
+++ b/MyTracks/res/values-it/strings.xml
@@ -147,14 +147,20 @@ limitations under the License.
Dettagli del percorso
Nome del percorso
+ Nome del percorso
Descrizione del percorso
+ Descrizione del percorso
Tipo di attività (ad es. escursionismo, ciclismo, corsa, arrampicata...)
+ Tipo di attività
Dettagli sul segnaposto
Nome del segnaposto
+ Nome del segnaposto
Descrizione del segnaposto
+ Descrizione del segnaposto
Tipo di segnaposto (ad es. edificio, buca, vista...)
+ Tipo di segnaposto
Impostazioni di My Tracks
diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml
index a23fb0bbf..b9e5daa5f 100644
--- a/MyTracks/res/values-ja/strings.xml
+++ b/MyTracks/res/values-ja/strings.xml
@@ -176,14 +176,20 @@ limitations under the License.
トラック詳細
トラック名
+ トラック名
トラック説明
+ トラック説明
活動タイプ(ハイキング, サイクリング、ランニング、登山...)
+ 活動タイプ
マーカー詳細
マーカー名
+ マーカー名
マーカー説明
+ マーカー説明
マーカータイプ(建物、洞穴、名所...)
+ マーカータイプ
My Tracks設定
diff --git a/MyTracks/res/values-pl/strings.xml b/MyTracks/res/values-pl/strings.xml
index f05d38f95..f83d9c0f1 100644
--- a/MyTracks/res/values-pl/strings.xml
+++ b/MyTracks/res/values-pl/strings.xml
@@ -149,14 +149,20 @@ limitations under the License.
Szczegóły trasy
Nazwa trasy
+ Nazwa trasy
Opis trasy
+ Opis trasy
Typ aktywności (np. wspinanie, jazda rowerem, bieganie...)
+ Typ aktywności
Szczegóły znacznika
Nazwa znacznika
+ Nazwa znacznika
Opis znacznika
+ Opis znacznika
Typ znacznika (np. budynek, jaskinia, punkt widokowy...)
+ Typ znacznika
Ustawienia My Tracks
diff --git a/MyTracks/res/values-pt-rBR/strings.xml b/MyTracks/res/values-pt-rBR/strings.xml
index 46f347e11..5086bae40 100644
--- a/MyTracks/res/values-pt-rBR/strings.xml
+++ b/MyTracks/res/values-pt-rBR/strings.xml
@@ -144,14 +144,20 @@ limitations under the License.
Detalhes do Caminho
Nome do Caminho
+ Nome do Caminho
Descrição do Caminho
+ Descrição do Caminho
Tipo de Atividade (por ex. caminhada, ciclismo, escalada...)
+ Tipo de Atividade
Detalhes do Marcador
Nome do Marcador
+ Nome do Marcador
Descrição do Marcador
+ Descrição do Marcador
Tipo do Marcador (por ex. Prédio, Mirante, Cachoeira...)
+ Tipo do Marcador
Configurações do My Tracks
diff --git a/MyTracks/res/values-ru/strings.xml b/MyTracks/res/values-ru/strings.xml
index 76361ce98..a48596e59 100644
--- a/MyTracks/res/values-ru/strings.xml
+++ b/MyTracks/res/values-ru/strings.xml
@@ -179,14 +179,20 @@ limitations under the License.
Трек детали
Название трека
+ Название трека
Описание трека
+ Описание трека
Тип деятельности (например: пешая прогулка, велосипедная прогулка, бег, восхождение...)
+ Тип деятельности
Маркер детали
Имя маркера
+ Имя маркера
Описание маркера
+ Описание маркера
Тип маркера (например: здание, выбоина, достопримечательность...)
+ Тип маркера
Настройки My Tracks
diff --git a/MyTracks/res/values-sv/strings.xml b/MyTracks/res/values-sv/strings.xml
index 950e8b1dc..e3a3ffbe0 100644
--- a/MyTracks/res/values-sv/strings.xml
+++ b/MyTracks/res/values-sv/strings.xml
@@ -149,14 +149,20 @@ limitations under the License.
Spårdetaljer
Spårnamn
+ Spårnamn
Spårbeskrivning
+ Spårbeskrivning
Aktivitetstyp (t ex vandring, cyckling, löpning, klättring...)
+ Aktivitetstyp
Markördetaljer
Markörnamn
+ Markörnamn
Markörbeskrivning
+ Markörbeskrivning
Markörtyp (e.g. byggnad, grop, sevärdhet...)
+ Markörtyp
My Tracks inställningar
diff --git a/MyTracks/res/values-zh-rCN/strings.xml b/MyTracks/res/values-zh-rCN/strings.xml
index 9f41d20c0..e15de3822 100644
--- a/MyTracks/res/values-zh-rCN/strings.xml
+++ b/MyTracks/res/values-zh-rCN/strings.xml
@@ -180,14 +180,20 @@ limitations under the License.
轨迹详细信息
轨迹名称
+ 轨迹名称
轨迹描述
+ 轨迹描述
活动类型 (例如: 徒步, 骑行, 跑步, 攀岩...)
+ 活动类型
标记详细信息
标记名称
+ 标记名称
标记描述
+ 标记描述
标记类型 (例如: 建筑, 陷坑, 景点...)
+ 标记类型
My Tracks 设置
diff --git a/MyTracks/res/values/arrays_do_not_translate.xml b/MyTracks/res/values/arrays_do_not_translate.xml
index 25f173930..89c48c308 100644
--- a/MyTracks/res/values/arrays_do_not_translate.xml
+++ b/MyTracks/res/values/arrays_do_not_translate.xml
@@ -118,4 +118,16 @@ limitations under the License.
- @string/ant_sensor_type
- @string/srm_ant_bridge_sensor_type
+
+
+ - @string/track_color_mode_none
+ - @string/track_color_mode_fixed
+ - @string/track_color_mode_dynamic
+
+
+
+ - @string/none
+ - @string/track_color_mode_fixed
+ - @string/track_color_mode_dynamic
+
diff --git a/MyTracks/res/values/colors.xml b/MyTracks/res/values/colors.xml
index 803446580..eca05f9d7 100644
--- a/MyTracks/res/values/colors.xml
+++ b/MyTracks/res/values/colors.xml
@@ -51,4 +51,14 @@ limitations under the License.
#EDEDED
#99999999
+
+
+
+ #F774DF00
+
+
+ #F75858FA
+
+
+ #F7DF0101
diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml
index 3f8a69c29..c7cd1fe93 100644
--- a/MyTracks/res/values/do_not_translate.xml
+++ b/MyTracks/res/values/do_not_translate.xml
@@ -49,4 +49,12 @@ limitations under the License.
android.intent.action.SAVE
+ android.intent.action.settings.OPEN
+ com.google.android.apps.mytracks.START_NEW_TRACK
+ com.google.android.apps.mytracks.END_CURRENT_TRACK
+
+
+
+
+ com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS
diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml
index 01f56da24..1bdfa390f 100644
--- a/MyTracks/res/values/keys.xml
+++ b/MyTracks/res/values/keys.xml
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
+ allowAccess
announcementFrequency
antHeartRateSensorId
antPowerSensorId
@@ -35,22 +36,34 @@ limitations under the License.
reportSpeed
sendStatsAndPoints
sendToDocs
- sendToMyMaps
sendToFusionTables
+ sendToMyMaps
sensorType
shareUrlOnly
splitFrequency
timestampTrackName
-
-
+ trackColorModeDynamicVariation
+
+ trackColorModeFixedMediumDisplay
+
+ trackColorModeFixedMedium
+ trackColorModeFixedSlowDisplay
+
+ trackColorModeFixedSlow
+ trackColorMode
+
+
autoResumeTrackCurrentRetry
recordingTrack
selectedTrack
-
- backupToSd
- restoreFromSd
- resetToDefaults
- sensorOptions
+
antOptions
+ backupToSd
+ displaySettingsScreen
+ resetToDefaults
+ restoreFromSd
+ sensorOptions
+ trackColorModeFixedOptions
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index afe2a8f2a..9a2d6198f 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -184,14 +184,20 @@ limitations under the License.
Track Details
Track Name
+ Track Name
Track Description
+ Track Description
Activity Type (e.g. hiking, cycling, running, climbing...)
+ Activity Type
Marker Details
Marker Name
+ Marker Name
Marker Description
+ Marker Description
Marker Type (e.g. Building, Pothole, Sight...)
+ Marker Type
My Tracks Settings
@@ -202,6 +208,15 @@ limitations under the License.
Not available in your version of Android
Not available while recording a track
+ Allow access
+ Other apps can read, write, start/stop recording
+ MyTracks data.
+ Other apps cannot read, write, start/stop
+ recording MyTracks data.
+ Are you sure you want to allow other apps,
+ including future apps, to control and access MyTracks data? There will be no further
+ notification.
+
Announcement frequency
Minimum time between two voice announcements
@@ -262,6 +277,17 @@ limitations under the License.
The bluetooth sensor to connect to
Modular Stats Display
+ Track Color Mode
+ The way to color the track
+ Fixed
+ Dynamic
+ Fixed Speed Slow
+ Slow speed top value
+ Fixed Speed Medium
+ Medium speed top value
+ Dynamic Speed Variation
+ % Margin from average speed to consider medium speed
+
Backup (BETA)
SD Card Backup
@@ -477,7 +503,10 @@ limitations under the License.
bluetoothSensorsEnabled
ANT options
Sensor Options
-
+ Track Color Mode
+ Fixed Speed Options
+ Dynamic Speed Options
+
Connected to %s
Bluetooth sensor connecting ...
@@ -497,12 +526,19 @@ limitations under the License.
Sending
- receive track recording notifications
- read recorded track data
- write recorded track data, start/stop recording tracks
- Be notified when a track starts or stops being recorded.
- Be able to read track data, such as points and waypoints.
- Be able to write track data, such as starting recording or adding waypoints.
+ receive MyTracks notifications
+ receive MyTracks notifications
+ read MyTracks data
+ write data to MyTracks and start/stop MyTracks
+ recording
+ Allows the application to receive notifications
+ when MyTracks starts or stops a recording.
+ receive MyTracks notifications
+
+ Allows the application to read MyTracks data.
+
+ Allows the application to write data to
+ MyTracks and start/stop MyTracks recording.
NONE
@@ -511,4 +547,8 @@ limitations under the License.
Polar Bluetooth
Zephyr Bluetooth
+
+ None
+ Fixed (BETA)
+ Dynamic (BETA)
diff --git a/MyTracks/res/values/styles.xml b/MyTracks/res/values/styles.xml
index 54287366c..0eca32c08 100644
--- a/MyTracks/res/values/styles.xml
+++ b/MyTracks/res/values/styles.xml
@@ -85,4 +85,22 @@ limitations under the License.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml
index 4ca9dbe59..24dcf68ed 100644
--- a/MyTracks/res/xml/preferences.xml
+++ b/MyTracks/res/xml/preferences.xml
@@ -18,7 +18,8 @@ limitations under the License.
xmlns:android="http://schemas.android.com/apk/res/android">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -156,6 +209,13 @@ limitations under the License.
android:summaryOn="@string/settings_share_url_only_summary_on"
android:summaryOff="@string/settings_share_url_only_summary_off"
android:defaultValue="false" />
+
+
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
index ca4ac742f..194912471 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
@@ -20,11 +20,11 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
-import com.google.android.apps.mytracks.content.TrackDataHub;
-import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
-import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.Track;
+import com.google.android.apps.mytracks.content.TrackDataHub;
+import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
+import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
@@ -148,7 +148,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
- dataHub = TrackDataHub.getStartedInstance();
+ dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
diff --git a/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java b/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java
index 556ad8f7d..355418599 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/CheckUnits.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+
import java.util.Locale;
/**
@@ -79,11 +80,13 @@ class CheckUnits {
private static void accept(Context context, SharedPreferences preferences) {
recordCheckPerformed(preferences);
Intent startIntent = new Intent(context, SettingsActivity.class);
+ startIntent.putExtra(context.getString(R.string.open_settings_screen),
+ context.getString(R.string.display_settings_screen_key));
context.startActivity(startIntent);
}
private static void recordCheckPerformed(SharedPreferences preferences) {
- ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
+ ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_UNITS_CHECKED, true));
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ColoredPath.java b/MyTracks/src/com/google/android/apps/mytracks/ColoredPath.java
new file mode 100644
index 000000000..be34ced2a
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/ColoredPath.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.android.apps.mytracks;
+
+import android.graphics.Paint;
+import android.graphics.Path;
+
+/**
+ * Represents a colored {@code Path} to save its relative color for drawing.
+ * @author Vangelis S.
+ */
+public class ColoredPath {
+ private final Path path;
+ private final Paint pathPaint;
+
+ /**
+ * Constructor for a ColoredPath by color.
+ */
+ public ColoredPath(int color) {
+ path = new Path();
+ pathPaint = new Paint();
+ pathPaint.setColor(color);
+ pathPaint.setStrokeWidth(3);
+ pathPaint.setStyle(Paint.Style.STROKE);
+ pathPaint.setAntiAlias(true);
+ }
+
+ /**
+ * Constructor for a ColoredPath by Paint.
+ */
+ public ColoredPath(Paint paint) {
+ path = new Path();
+ pathPaint = paint;
+ }
+
+ /**
+ * @return the path
+ */
+ public Path getPath() {
+ return path;
+ }
+
+ /**
+ * @return the pathPaint
+ */
+ public Paint getPathPaint() {
+ return pathPaint;
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java
index 55516eb36..2338b5b39 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/DeleteAllTracks.java
@@ -65,7 +65,7 @@ public class DeleteAllTracks extends Handler {
SharedPreferences.Editor editor = prefs.edit();
// TODO: Go through data manager
editor.putLong(context.getString(R.string.selected_track_key), -1);
- ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
+ ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
if (done != null) {
Handler h = new Handler();
h.post(done);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/DeleteTrack.java b/MyTracks/src/com/google/android/apps/mytracks/DeleteTrack.java
index 1b73e8f68..bc53c8fed 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/DeleteTrack.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/DeleteTrack.java
@@ -108,7 +108,7 @@ public class DeleteTrack extends Activity
SharedPreferences preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
Editor editor = preferences.edit().putLong(selectedKey, -1);
- ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
+ ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(editor);
}
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/Eula.java b/MyTracks/src/com/google/android/apps/mytracks/Eula.java
index c54926795..59936ce80 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/Eula.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/Eula.java
@@ -95,7 +95,7 @@ class Eula {
}
private static void accept(Activity activity, SharedPreferences preferences) {
- ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
+ ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putBoolean(PREFERENCE_EULA_ACCEPTED, true));
Intent startIntent = new Intent(activity, WelcomeActivity.class);
activity.startActivityForResult(startIntent, Constants.WELCOME);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
index e36c76197..dfea7d5d5 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java
@@ -217,4 +217,4 @@ public class ImportAllTracks {
}
return gpxFiles;
}
-}
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
index f168b3bc7..d7f5b8ce8 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
@@ -185,7 +185,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
Log.d(TAG, "MapActivity.onResume");
super.onResume();
- dataHub = TrackDataHub.getStartedInstance();
+ dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.POINT_UPDATES,
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
index 0203d8d6b..b4bb30db7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapOverlay.java
@@ -18,6 +18,9 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Waypoint;
+import com.google.android.apps.mytracks.maps.TrackPathPainter;
+import com.google.android.apps.mytracks.maps.TrackPathPainterFactory;
+import com.google.android.apps.mytracks.maps.TrackPathUtilities;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
@@ -27,10 +30,11 @@ import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -48,7 +52,7 @@ import java.util.concurrent.BlockingQueue;
*
* @author Leif Hendrik Wilden
*/
-public class MapOverlay extends Overlay {
+public class MapOverlay extends Overlay implements OnSharedPreferenceChangeListener {
private final Drawable[] arrows;
private final int arrowWidth, arrowHeight;
@@ -57,7 +61,6 @@ public class MapOverlay extends Overlay {
private final Drawable startMarker;
private final Drawable endMarker;
private final int markerWidth, markerHeight;
- private final Paint selectedTrackPaint;
private final Paint errorCirclePaint;
private final Context context;
private final List waypoints;
@@ -73,22 +76,26 @@ public class MapOverlay extends Overlay {
private GeoPoint lastReferencePoint;
private Rect lastViewRect;
- private Path lastPath;
+ private boolean lastPathExists;
+ private Location lastLocation;
+ private TrackPathPainter trackPathPainter;
/**
* Represents a pre-processed {@code Location} to speed up drawing.
* This class is more like a data object and doesn't provide accessors.
*/
- private static class CachedLocation {
+ public static class CachedLocation {
public final boolean valid;
public final GeoPoint geoPoint;
-
+ public final int speed;
+
/**
* Constructor for an invalid cached location.
*/
public CachedLocation() {
this.valid = false;
this.geoPoint = null;
+ this.speed = -1;
}
/**
@@ -97,6 +104,7 @@ public class MapOverlay extends Overlay {
public CachedLocation(Location location) {
this.valid = LocationUtils.isValidLocation(location);
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
+ this.speed = (int) Math.floor(location.getSpeed() * 3.6);
}
};
@@ -151,18 +159,13 @@ public class MapOverlay extends Overlay {
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
- selectedTrackPaint = new Paint();
- selectedTrackPaint.setColor(resources.getColor(R.color.red));
- selectedTrackPaint.setStrokeWidth(3);
- selectedTrackPaint.setStyle(Paint.Style.STROKE);
- selectedTrackPaint.setAntiAlias(true);
-
- errorCirclePaint = new Paint();
- errorCirclePaint.setColor(resources.getColor(R.color.blue));
- errorCirclePaint.setStyle(Paint.Style.STROKE);
- errorCirclePaint.setStrokeWidth(3);
+ errorCirclePaint = TrackPathUtilities.getPaint(R.color.blue, context);
errorCirclePaint.setAlpha(127);
- errorCirclePaint.setAntiAlias(true);
+
+ trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
+
+ context.getSharedPreferences(Constants.SETTINGS_NAME, 0)
+ .registerOnSharedPreferenceChangeListener(this);
}
/**
@@ -205,18 +208,19 @@ public class MapOverlay extends Overlay {
}
// Visible for testing.
- int getNumWaypoints() {
+ public int getNumWaypoints() {
synchronized (waypoints) {
return waypoints.size();
}
}
public void clearPoints() {
- synchronized (points) {
- points.clear();
+ synchronized (getPoints()) {
+ getPoints().clear();
pendingPoints.clear();
- lastPath = null;
+ lastPathExists = false;
lastViewRect = null;
+ trackPathPainter.clear();
}
}
@@ -254,6 +258,9 @@ public class MapOverlay extends Overlay {
// Draw the selected track:
drawTrack(canvas, projection, viewRect);
+ // Draw the "Start" and "End" markers:
+ drawMarkers(canvas, projection);
+
// Draw the waypoints:
drawWaypoints(canvas, projection);
}
@@ -262,6 +269,28 @@ public class MapOverlay extends Overlay {
drawMyLocation(canvas, projection);
}
+ private void drawMarkers(Canvas canvas, Projection projection) {
+ // Draw the "End" marker.
+ if (showEndMarker) {
+ for (int i = getPoints().size() - 1; i >= 0; --i) {
+ if (getPoints().get(i).valid) {
+ drawElement(canvas, projection, getPoints().get(i).geoPoint, endMarker,
+ -markerWidth / 2, -markerHeight);
+ break;
+ }
+ }
+ }
+
+ // Draw the "Start" marker.
+ for (int i = 0; i < getPoints().size(); ++i) {
+ if (getPoints().get(i).valid) {
+ drawElement(canvas, projection, getPoints().get(i).geoPoint, startMarker,
+ -markerWidth / 2, -markerHeight);
+ break;
+ }
+ }
+ }
+
// Visible for testing.
Projection getMapProjection(MapView mapView) {
return mapView.getProjection();
@@ -275,15 +304,15 @@ public class MapOverlay extends Overlay {
int cy = mapView.getMapCenter().getLatitudeE6();
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
}
-
- // Visible for testing.
- Path newPath() {
- return new Path();
+
+ // For use in testing only.
+ public TrackPathPainter getTrackPathPainter() {
+ return trackPathPainter;
}
-
- // Visible for testing.
- Path getLastPath() {
- return lastPath;
+
+ // For use in testing only.
+ public void setTrackPathPainter(TrackPathPainter trackPathPainter) {
+ this.trackPathPainter = trackPathPainter;
}
private void drawWaypoints(Canvas canvas, Projection projection) {
@@ -311,96 +340,39 @@ public class MapOverlay extends Overlay {
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
}
- private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
- Path path;
+ private void drawTrack(Canvas canvas, Projection projection, Rect viewRect)
+ {
+ boolean draw;
+
synchronized (points) {
// Merge the pending points with the list of cached locations.
final GeoPoint referencePoint = projection.fromPixels(0, 0);
- int newPoints = pendingPoints.drainTo(points);
+ int newPoints = pendingPoints.drainTo(points);
boolean newProjection = !viewRect.equals(lastViewRect) ||
- !referencePoint.equals(lastReferencePoint);
- if (newPoints == 0 && lastPath != null && !newProjection) {
+ !referencePoint.equals(lastReferencePoint);
+ if (newPoints == 0 && lastPathExists && !newProjection) {
// No need to recreate path (same points and viewing area).
- path = lastPath;
+ draw = true;
} else {
int numPoints = points.size();
if (numPoints < 2) {
// Not enough points to draw a path.
- path = null;
- } else if (lastPath != null && !newProjection) {
+ draw = false;
+ } else if (!trackPathPainter.needsRedraw() && lastPathExists && !newProjection) {
// Incremental update of the path, without repositioning the view.
- path = lastPath;
- updatePath(projection, viewRect, path, numPoints - newPoints);
+ draw = true;
+ trackPathPainter.updatePath(projection, viewRect, numPoints - newPoints, alwaysVisible, points);
} else {
// The view has changed so we have to start from scratch.
- path = newPath();
- path.incReserve(numPoints);
- updatePath(projection, viewRect, path, 0);
+ draw = true;
+ trackPathPainter.updatePath(projection, viewRect, 0, alwaysVisible, points);
}
- lastPath = path;
}
lastReferencePoint = referencePoint;
lastViewRect = viewRect;
}
- if (path != null) {
- canvas.drawPath(path, selectedTrackPaint);
- }
-
- // Draw the "End" marker.
- if (showEndMarker) {
- for (int i = points.size() - 1; i >= 0; --i) {
- if (points.get(i).valid) {
- drawElement(canvas, projection, points.get(i).geoPoint, endMarker,
- -markerWidth / 2, -markerHeight);
- break;
- }
- }
- }
-
- // Draw the "Start" marker.
- for (int i = 0; i < points.size(); ++i) {
- if (points.get(i).valid) {
- drawElement(canvas, projection, points.get(i).geoPoint, startMarker,
- -markerWidth / 2, -markerHeight);
- break;
- }
- }
- }
-
- private void updatePath(Projection projection, Rect viewRect, Path path,
- int startLocationIdx) {
- // Whether to start a new segment on new valid and visible point.
- boolean newSegment =
- startLocationIdx > 0 ? !points.get(startLocationIdx - 1).valid : true;
- boolean lastVisible = !newSegment;
- final Point pt = new Point();
- // Loop over track points.
- for (int i = startLocationIdx; i < points.size(); ++i) {
- CachedLocation loc = points.get(i);
- // Check if valid, if not then indicate a new segment.
- if (!loc.valid) {
- newSegment = true;
- continue;
- }
-
- final GeoPoint geoPoint = loc.geoPoint;
- // Check if this breaks the existing segment.
- boolean visible = alwaysVisible || viewRect.contains(
- geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
- if (!visible && !lastVisible) {
- // This is a point outside view not connected to a visible one.
- newSegment = true;
- }
- lastVisible = visible;
-
- // Either move to beginning of a new segment or continue the old one.
- projection.toPixels(geoPoint, pt);
- if (newSegment) {
- path.moveTo(pt.x, pt.y);
- newSegment = false;
- } else {
- path.lineTo(pt.x, pt.y);
- }
+ if (draw) {
+ trackPathPainter.drawTrack(canvas);
}
}
@@ -479,4 +451,21 @@ public class MapOverlay extends Overlay {
}
return super.onTap(p, mapView);
}
+
+ /**
+ * @return the points
+ */
+ public List getPoints() {
+ return points;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Log.d(TAG, "MapOverlay: onSharedPreferences changed " + key);
+ if (key != null) {
+ if (key.equals(context.getString(R.string.track_color_mode_key))) {
+ trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
+ }
+ }
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
index 8a9555a8e..6da1bb12b 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
@@ -134,7 +134,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
super.onCreate(savedInstanceState);
ApiFeatures apiFeatures = ApiFeatures.getInstance();
if (!SystemUtils.isRelease(this)) {
- apiFeatures.getApiPlatformAdapter().enableStrictMode();
+ apiFeatures.getApiAdapter().enableStrictMode();
}
tracker = GoogleAnalyticsTracker.getInstance();
@@ -146,7 +146,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
providerUtils = MyTracksProviderUtils.Factory.get(this);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
- dataHub = TrackDataHub.newInstance(this);
+ dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
menuManager = new MenuManager(this);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
@@ -249,7 +249,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
protected void onDestroy() {
Log.d(TAG, "MyTracks.onDestroy");
serviceConnection.unbind();
-
super.onDestroy();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java
new file mode 100644
index 000000000..27cf04efb
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksApplication.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import com.google.android.apps.mytracks.content.TrackDataHub;
+
+import android.app.Application;
+
+/**
+ * MyTracksApplication for keeping global state.
+ *
+ * @author jshih@google.com (Jimmy Shih)
+ */
+public class MyTracksApplication extends Application {
+
+ private TrackDataHub trackDataHub;
+
+ /**
+ * Gets the application's TrackDataHub.
+ *
+ * Note: use synchronized to make sure only one instance is created per application.
+ */
+ public synchronized TrackDataHub getTrackDataHub() {
+ if (trackDataHub == null) {
+ trackDataHub = TrackDataHub.newInstance(getApplicationContext());
+ }
+ return trackDataHub;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
index c2e2d634c..2729ef423 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
@@ -20,6 +20,8 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
+import com.google.android.apps.mytracks.services.sensors.SensorManager;
+import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -55,7 +57,8 @@ public class SensorStateActivity extends Activity {
private final Runnable stateUpdater = new Runnable() {
public void run() {
- updateState();
+ if (isVisible) // only update when UI is visible
+ updateState();
}
};
@@ -73,6 +76,23 @@ public class SensorStateActivity extends Activity {
runOnUiThread(stateUpdater);
}
};
+
+
+ /**
+ * A temporary sensor manager, when none is available.
+ */
+ SensorManager tempSensorManager = null;
+
+ /**
+ * A state flag set to true when the activity is active/visible,
+ * i.e. after resume, and before pause
+ *
+ * Used to avoid updating after the pause event, because sometimes an update
+ * event occurs even after the timer is cancelled. In this case,
+ * it could cause the {@link #tempSensorManager} to be recreated, after it
+ * is destroyed at the pause event.
+ */
+ boolean isVisible = false;
public SensorStateActivity() {
utils = new StatsUtilities(this);
@@ -86,7 +106,6 @@ public class SensorStateActivity extends Activity {
setContentView(R.layout.sensor_state);
- // TODO: Allow reading from sensors even if service is not running.
serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
serviceConnection.bindIfRunning();
updateState();
@@ -95,6 +114,8 @@ public class SensorStateActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
+
+ isVisible = true;
serviceConnection.bindIfRunning();
@@ -104,9 +125,12 @@ public class SensorStateActivity extends Activity {
@Override
protected void onPause() {
+ isVisible = false;
+
timer.cancel();
timer.purge();
timer = null;
+ stopTempSensorManager();
super.onPause();
}
@@ -117,35 +141,106 @@ public class SensorStateActivity extends Activity {
}
protected void updateState() {
+ Log.d(TAG, "Updating SensorStateActivity");
+
ITrackRecordingService service = serviceConnection.getServiceIfBound();
- if (service == null) {
- Log.d(Constants.TAG, "Could not get track recording service.");
- updateSensorState(Sensor.SensorState.NONE);
- updateSensorData(null);
- return;
- }
-
- Sensor.SensorDataSet sds = null;
- try {
- byte[] buff = service.getSensorData();
- if (buff != null) {
- sds = Sensor.SensorDataSet.parseFrom(buff);
- updateSensorData(sds);
+
+ // Check if service is available, and recording.
+ boolean isRecording = false;
+ if (service != null) {
+ try {
+ isRecording = service.isRecording();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to determine if service is recording.", e);
}
- } catch (RemoteException e) {
- Log.e(Constants.TAG, "Could not read sensor data.", e);
- } catch (InvalidProtocolBufferException e) {
- Log.e(Constants.TAG, "Could not read sensor data.", e);
}
- updateSensorData(sds);
+
+ // If either service isn't available, or not recording.
+ if (!isRecording) {
+ updateFromTempSensorManager();
+ } else {
+ updateFromSysSensorManager();
+ }
+ }
+
+ private void updateFromTempSensorManager()
+ {
+ // Use variables to hold the sensor state and data set.
+ Sensor.SensorState currentState = null;
+ Sensor.SensorDataSet currentDataSet = null;
+
+ // If no temp sensor manager is present, create one, and start it.
+ if (tempSensorManager == null) {
+ tempSensorManager = SensorManagerFactory.getSensorManager(this);
+ if (tempSensorManager != null)
+ tempSensorManager.onStartTrack();
+ }
+
+ // If a temp sensor manager is available, use states from temp sensor
+ // manager.
+ if (tempSensorManager != null) {
+ currentState = tempSensorManager.getSensorState();
+ currentDataSet = tempSensorManager.getSensorDataSet();
+ }
- try {
- int i = service.getSensorState();
- updateSensorState(Sensor.SensorState.valueOf(i));
- } catch (RemoteException e) {
- Log.e(Constants.TAG, "Could not read sensor state.", e);
- updateSensorState(Sensor.SensorState.NONE);
+ // Update the sensor state, and sensor data, using the variables.
+ updateSensorStateAndData(currentState, currentDataSet);
+ }
+
+ private void updateFromSysSensorManager()
+ {
+ // Use variables to hold the sensor state and data set.
+ Sensor.SensorState currentState = null;
+ Sensor.SensorDataSet currentDataSet = null;
+
+ ITrackRecordingService service = serviceConnection.getServiceIfBound();
+
+ // If a temp sensor manager is present, shut it down,
+ // probably recording just started.
+ stopTempSensorManager();
+
+ // Get sensor details from the service.
+ if (service == null) {
+ Log.d(TAG, "Could not get track recording service.");
+ } else {
+ try {
+ byte[] buff = service.getSensorData();
+ if (buff != null) {
+ currentDataSet = Sensor.SensorDataSet.parseFrom(buff);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not read sensor data.", e);
+ } catch (InvalidProtocolBufferException e) {
+ Log.e(TAG, "Could not read sensor data.", e);
+ }
+
+ try {
+ currentState = Sensor.SensorState.valueOf(service.getSensorState());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not read sensor state.", e);
+ currentState = Sensor.SensorState.NONE;
+ }
}
+
+ // Update the sensor state, and sensor data, using the variables.
+ updateSensorStateAndData(currentState, currentDataSet);
+ }
+
+ /**
+ * Stops the temporary sensor manager, if one exists.
+ */
+ private void stopTempSensorManager()
+ {
+ if (tempSensorManager != null) {
+ tempSensorManager.shutdown();
+ tempSensorManager = null;
+ }
+ }
+
+ private void updateSensorStateAndData(Sensor.SensorState state, Sensor.SensorDataSet dataSet)
+ {
+ updateSensorState(state == null ? Sensor.SensorState.NONE : state);
+ updateSensorData(dataSet);
}
private void updateSensorState(Sensor.SensorState state) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
index b2316ab29..b4e2ffd62 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
+import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
@@ -32,11 +33,13 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.Settings;
@@ -50,7 +53,11 @@ import java.util.Set;
/**
* An activity that let's the user see and edit the settings.
- *
+ *
+ * This activity has two entry points, "root" and "display" preference screen.
+ * If bundle.getString("open_settings_screen") is set to "display_settings_screen_key", then
+ * the "display" preference screen is shown, otherwise, the "root" display preference is shown.
+ *
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@@ -58,12 +65,22 @@ public class SettingsActivity extends PreferenceActivity {
private BackupPreferencesListener backupListener;
private SharedPreferences preferences;
-
+
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ initActivityCommons();
+
+ // If we only need the display setting screen nothing else needs to load.
+ if (processIntent())
+ return;
+
+ initActivitySpecifics();
+ }
+
+ private void initActivityCommons() {
// The volume we want to control is the Text-To-Speech volume
ApiFeatures apiFeatures = ApiFeatures.getInstance();
int volumeStream =
@@ -76,13 +93,26 @@ public class SettingsActivity extends PreferenceActivity {
preferenceManager.setSharedPreferencesMode(0);
// Set up automatic preferences backup
- backupListener = BackupPreferencesListener.create(this, apiFeatures);
+ backupListener = apiFeatures.getApiAdapter().getBackupPreferencesListener(this);
preferences = preferenceManager.getSharedPreferences();
preferences.registerOnSharedPreferenceChangeListener(backupListener);
// Load the preferences to be displayed
addPreferencesFromResource(R.xml.preferences);
+ // Disable TTS announcement preference if not available
+ if (!apiFeatures.hasTextToSpeech()) {
+ IntegerListPreference announcementFrequency =
+ (IntegerListPreference) findPreference(
+ getString(R.string.announcement_frequency_key));
+ announcementFrequency.setEnabled(false);
+ announcementFrequency.setValue("-1");
+ announcementFrequency.setSummary(
+ R.string.settings_not_available_summary);
+ }
+ }
+
+ private void initActivitySpecifics() {
// Hook up switching of displayed list entries between metric and imperial
// units
CheckBoxPreference metricUnitsPreference =
@@ -101,18 +131,8 @@ public class SettingsActivity extends PreferenceActivity {
updatePreferenceUnits(metricUnitsPreference.isChecked());
customizeSensorOptionsPreferences();
-
- // Disable TTS announcement preference if not available
- if (!apiFeatures.hasTextToSpeech()) {
- IntegerListPreference announcementFrequency =
- (IntegerListPreference) findPreference(
- getString(R.string.announcement_frequency_key));
- announcementFrequency.setEnabled(false);
- announcementFrequency.setValue("-1");
- announcementFrequency.setSummary(
- R.string.settings_not_available_summary);
- }
-
+ customizeTrackColorModePreferences();
+
// Hook up action for resetting all settings
Preference resetPreference = findPreference(getString(R.string.reset_key));
resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -122,6 +142,50 @@ public class SettingsActivity extends PreferenceActivity {
return true;
}
});
+
+ // Add a confirmation dialog for the "Allow access" preference.
+ final CheckBoxPreference allowAccessPreference = (CheckBoxPreference) findPreference(
+ getString(R.string.allow_access_key));
+ allowAccessPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if ((Boolean) newValue) {
+ AlertDialog dialog = new AlertDialog.Builder(SettingsActivity.this)
+ .setCancelable(true)
+ .setTitle(getString(R.string.settings_allow_access))
+ .setMessage(getString(R.string.settings_allow_access_dialog_message))
+ .setPositiveButton(android.R.string.ok, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int button) {
+ allowAccessPreference.setChecked(true);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ dialog.show();
+ return false;
+ } else {
+ return true;
+ }
+ }
+ });
+ }
+
+ private boolean processIntent() {
+ boolean showDisplaySettings = false;
+ Bundle bundle = getIntent().getExtras();
+ PreferenceScreen preferenceScreen;
+ String intentString = getString(R.string.open_settings_screen);
+
+ if (bundle != null) {
+ preferenceScreen = (PreferenceScreen) findPreference(bundle.getString(intentString));
+ if (preferenceScreen != null) {
+ showDisplaySettings = true;
+ setPreferenceScreen(preferenceScreen);
+ }
+ }
+
+ return showDisplaySettings;
}
private void customizeSensorOptionsPreferences() {
@@ -170,10 +234,37 @@ public class SettingsActivity extends PreferenceActivity {
sensorOptionsScreen.removePreference(findPreference(getString(R.string.ant_options_key)));
}
}
+
+ private void customizeTrackColorModePreferences() {
+ ListPreference trackColorModePreference =
+ (ListPreference) findPreference(getString(R.string.track_color_mode_key));
+ trackColorModePreference.setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference,
+ Object newValue) {
+ updateTrackColorModeSettings((String) newValue);
+ return true;
+ }
+ });
+ updateTrackColorModeSettings(trackColorModePreference.getValue());
+
+ setTrackColorModePreferenceListeners();
+
+ PreferenceCategory speedOptionsCategory =
+ (PreferenceCategory) findPreference(getString(R.string.track_color_mode_fixed_speed_options_key));
+
+ speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_slow_key)));
+ speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_medium_key)));
+ }
@Override
protected void onResume() {
super.onResume();
+
+ // If we only need the display setting screen nothing else needs to load.
+ if (processIntent())
+ return;
configureBluetoothPreferences();
Preference backupNowPreference =
@@ -251,6 +342,18 @@ public class SettingsActivity extends PreferenceActivity {
}
}
+ private void updateTrackColorModeSettings(String trackColorMode) {
+ boolean usesFixedSpeed = trackColorMode.equals(getString(R.string.track_color_mode_fixed));
+ boolean usesDynamicSpeed = trackColorMode.equals(getString(R.string.track_color_mode_dynamic));
+
+ findPreference(
+ getString(R.string.track_color_mode_fixed_speed_slow_display_key)).setEnabled(usesFixedSpeed);
+ findPreference(
+ getString(R.string.track_color_mode_fixed_speed_medium_display_key)).setEnabled(usesFixedSpeed);
+ findPreference(
+ getString(R.string.track_color_mode_dynamic_speed_variation_key)).setEnabled(usesDynamicSpeed);
+ }
+
/**
* Updates all the preferences which give options with distance units to use
* the proper unit the user has selected.
@@ -379,4 +482,84 @@ public class SettingsActivity extends PreferenceActivity {
}
}.start();
}
+
+ /**
+ * Set the given edit text preference text.
+ * If the units are not metric convert the value before displaying.
+ */
+ private void viewTrackColorModeSettings(EditTextPreference preference, int id) {
+ CheckBoxPreference metricUnitsPreference = (CheckBoxPreference) findPreference(
+ getString(R.string.metric_units_key));
+ if(metricUnitsPreference.isChecked()) {
+ return;
+ }
+ // Convert miles/h to km/h
+ SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ String metricspeed = prefs.getString(getString(id), null);
+ int englishspeed;
+ try {
+ englishspeed = (int) (Double.parseDouble(metricspeed) * UnitConversions.KMH_TO_MPH);
+ } catch (NumberFormatException e) {
+ englishspeed = 0;
+ }
+ preference.getEditText().setText(String.valueOf(englishspeed));
+ }
+
+ /**
+ * Saves the given edit text preference value.
+ * If the units are not metric convert the value before saving.
+ */
+ private void validateTrackColorModeSettings(EditTextPreference preference,
+ String newValue, int id) {
+ CheckBoxPreference metricUnitsPreference = (CheckBoxPreference) findPreference(
+ getString(R.string.metric_units_key));
+ String metricspeed;
+ if(!metricUnitsPreference.isChecked()) {
+ // Convert miles/h to km/h
+ try {
+ metricspeed = String.valueOf((int) (Double.parseDouble(newValue) * UnitConversions.MPH_TO_KMH) + 1);
+ } catch (NumberFormatException e) {
+ metricspeed = "0";
+ }
+ } else {
+ metricspeed = newValue;
+ }
+ SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ prefs.edit().putString(getString(id), metricspeed).commit();
+ }
+
+ /**
+ * Sets the TrackColorMode preference listeners.
+ */
+ private void setTrackColorModePreferenceListeners() {
+ setTrackColorModePreferenceListener(R.string.track_color_mode_fixed_speed_slow_display_key,
+ R.string.track_color_mode_fixed_speed_slow_key);
+ setTrackColorModePreferenceListener(R.string.track_color_mode_fixed_speed_medium_display_key,
+ R.string.track_color_mode_fixed_speed_medium_key);
+ }
+
+ /**
+ * Sets a TrackColorMode preference listener.
+ */
+ private void setTrackColorModePreferenceListener(int displayKey, final int metricKey) {
+ EditTextPreference trackColorModePreference =
+ (EditTextPreference) findPreference(getString(displayKey));
+ trackColorModePreference.setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference,
+ Object newValue) {
+ validateTrackColorModeSettings((EditTextPreference) preference, (String) newValue, metricKey);
+ return true;
+ }
+ });
+ trackColorModePreference.setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ viewTrackColorModeSettings((EditTextPreference) preference, metricKey);
+ return true;
+ }
+ });
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
index 5ed8b12b9..ba85fac47 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
@@ -137,7 +137,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
- dataHub = TrackDataHub.getStartedInstance();
+ dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java
index a4a32436d..92d04fad9 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointDetails.java
@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
@@ -139,6 +140,7 @@ public class WaypointDetails extends Activity
utils.setAllStats(waypointStats);
utils.setAltitude(
R.id.elevation_register, waypoint.getLocation().getAltitude());
+ name.setImeOptions(EditorInfo.IME_ACTION_DONE);
break;
}
icon.setImageDrawable(getResources().getDrawable(iconId));
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 bcf874c35..af3d57550 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java
@@ -17,11 +17,13 @@ package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.ApiFeatures;
+import com.google.android.maps.mytracks.R;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
@@ -30,6 +32,8 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
+import android.os.Binder;
+import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@@ -183,8 +187,22 @@ public class MyTracksProvider extends ContentProvider {
"waypoints/#", WAYPOINTS_ID);
}
+ private boolean canAccess() {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return true;
+ } else {
+ Context context = getContext();
+ SharedPreferences sharedPreferences = context.getSharedPreferences(
+ Constants.SETTINGS_NAME, 0);
+ return sharedPreferences.getBoolean(context.getString(R.string.allow_access_key), false);
+ }
+ }
+
@Override
public boolean onCreate() {
+ if (!canAccess()) {
+ return false;
+ }
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
try {
db = dbHelper.getWritableDatabase();
@@ -196,6 +214,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int delete(Uri url, String where, String[] selectionArgs) {
+ if (!canAccess()) {
+ return 0;
+ }
String table;
boolean shouldVacuum = false;
switch (urlMatcher.match(url)) {
@@ -228,6 +249,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public String getType(Uri url) {
+ if (!canAccess()) {
+ return null;
+ }
switch (urlMatcher.match(url)) {
case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE;
@@ -248,6 +272,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public Uri insert(Uri url, ContentValues initialValues) {
+ if (!canAccess()) {
+ return null;
+ }
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert");
ContentValues values;
if (initialValues != null) {
@@ -276,6 +303,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
+ if (!canAccess()) {
+ return 0;
+ }
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0;
try {
@@ -348,6 +378,9 @@ public class MyTracksProvider extends ContentProvider {
public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs,
String sort) {
+ if (!canAccess()) {
+ return null;
+ }
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = urlMatcher.match(url);
String sortOrder = null;
@@ -399,6 +432,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int update(Uri url, ContentValues values, String where,
String[] selectionArgs) {
+ if (!canAccess()) {
+ return 0;
+ }
int count;
int match = urlMatcher.match(url);
if (match == TRACKPOINTS) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
index f63e4456d..5df87c06a 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/TrackDataHub.java
@@ -195,15 +195,12 @@ public class TrackDataHub {
private int lastSamplingFrequency;
private DoubleBufferedLocationFactory locationFactory;
- private static TrackDataHub startedInstance;
-
+ private boolean started = false;
+
/**
* Builds a new {@link TrackDataHub} instance.
*/
public synchronized static TrackDataHub newInstance(Context context) {
- // Ensure our singleton is never bound to an activity, to avoid memory leaks.
- context = context.getApplicationContext();
-
SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
return new TrackDataHub(context,
@@ -240,11 +237,11 @@ public class TrackDataHub {
*/
public void start() {
Log.i(TAG, "TrackDataHub.start");
- if (startedInstance != null) {
+ if (isStarted()) {
Log.w(TAG, "Already started, ignoring");
return;
}
- startedInstance = this;
+ started = true;
listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start();
@@ -266,20 +263,6 @@ public class TrackDataHub {
return new DataSourcesWrapperImpl(context, preferences);
}
- /**
- * If there's an instance for which {@link start} has been called, returns it.
- *
- * @return the started instance
- * @throws IllegalStateException if there isn't a started instance
- */
- public static TrackDataHub getStartedInstance() {
- if (startedInstance == null) {
- throw new IllegalStateException("Data hub not started");
- }
-
- return startedInstance;
- }
-
/**
* Stops listening to data sources and reporting the data to external
* listeners.
@@ -290,12 +273,12 @@ public class TrackDataHub {
Log.w(TAG, "Not started, ignoring");
return;
}
-
+
// Unregister internal listeners even if there are external listeners registered.
dataSourceManager.unregisterAllListeners();
listenerHandlerThread.getLooper().quit();
- startedInstance = null;
+ started = false;
dataSources = null;
dataSourceManager = null;
@@ -304,7 +287,7 @@ public class TrackDataHub {
}
private boolean isStarted() {
- return startedInstance != null;
+ return started;
}
@Override
@@ -415,7 +398,7 @@ public class TrackDataHub {
// Save the selection to memory and flush.
selectedTrackId = trackId;
- ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
+ ApiFeatures.getInstance().getApiAdapter().applyPreferenceChanges(
preferences.edit().putLong(SELECTED_TRACK_KEY, trackId));
// Force it to reload data from the beginning.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
index f830abe6d..d7f35df06 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/SendToFusionTables.java
@@ -26,20 +26,21 @@ 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.ApiFeatures;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
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.MethodOverrideIntercepter;
+import com.google.api.client.googleapis.MethodOverride;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpRequestFactory;
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.javanet.NetHttpTransport;
import com.google.api.client.util.Strings;
import android.app.Activity;
@@ -64,6 +65,8 @@ import java.util.Vector;
*/
public class SendToFusionTables implements Runnable {
+ private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
+
/**
* Listener invoked when sending to fusion tables completes.
*/
@@ -118,22 +121,15 @@ public class SendToFusionTables implements Runnable {
private int totalLocations;
private int totalSegmentsUploaded;
- private HttpTransport transport;
+ private HttpRequestFactory httpRequestFactory;
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";
- static {
- // We manually assign the transport to avoid having HttpTransport try to
- // load it via reflection (which breaks due to ProGuard).
- HttpTransport.setLowLevelHttpTransport(new NetHttpTransport());
- }
-
- public SendToFusionTables(Activity context, AuthManager auth,
- long trackId, ProgressIndicator progressIndicator,
- OnSendCompletedListener onCompletion) {
+ public SendToFusionTables(Activity context, AuthManager auth, long trackId,
+ ProgressIndicator progressIndicator, OnSendCompletedListener onCompletion) {
this.context = context;
this.auth = auth;
this.trackId = trackId;
@@ -142,13 +138,8 @@ public class SendToFusionTables implements Runnable {
this.stringUtils = new StringUtils(context);
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
- GoogleHeaders headers = new GoogleHeaders();
- headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
- headers.gdataVersion = GDATA_VERSION;
-
- transport = new HttpTransport();
- MethodOverrideIntercepter.setAsFirstFor(transport);
- transport.defaultHeaders = headers;
+ HttpTransport transport = ApiFeatures.getInstance().getApiAdapter().getHttpTransport();
+ httpRequestFactory = transport.createRequestFactory(new MethodOverride());
}
@Override
@@ -177,7 +168,6 @@ public class SendToFusionTables implements Runnable {
}
private void doUpload() {
- ((GoogleHeaders) transport.defaultHeaders).setGoogleLogin(auth.getAuthToken());
int statusMessageId = R.string.error_sending_to_fusiontables;
boolean success = true;
try {
@@ -280,7 +270,7 @@ public class SendToFusionTables implements Runnable {
*
* @param name the marker name
* @param description the marker description
- * @param the marker location
+ * @param location the marker location
* @return true in case of success.
*/
private boolean createNewPoint(String name, String description, Location location,
@@ -306,7 +296,6 @@ public class SendToFusionTables implements Runnable {
}
private boolean uploadAllTrackPoints(final Track track, String originalDescription) {
-
SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
boolean metricUnits = true;
if (preferences != null) {
@@ -440,10 +429,10 @@ public class SendToFusionTables implements Runnable {
}
/**
- * Returns a KML Point tag for the given location.
+ * Returns a KML LineString.
*
- * @param location The location.
- * @return the kml.
+ * @param track the track.
+ * @return the KML LineString.
*/
private String getKmlLineString(Track track) {
StringBuilder builder = new StringBuilder("");
@@ -500,7 +489,7 @@ public class SendToFusionTables implements Runnable {
* 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
+ * @param locations locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
@@ -665,33 +654,40 @@ public class SendToFusionTables implements Runnable {
* @return true in case of success
*/
private boolean runUpdate(final String query) {
- GDataWrapper wrapper = new GDataWrapper();
+ GDataWrapper wrapper = new GDataWrapper();
wrapper.setAuthManager(auth);
wrapper.setRetryOnAuthFailure(true);
- wrapper.setClient(transport);
+ wrapper.setClient(httpRequestFactory);
Log.d(Constants.TAG, "GData connection prepared: " + this.auth);
- wrapper.runQuery(new QueryFunction() {
+ wrapper.runQuery(new QueryFunction() {
@Override
- public void query(HttpTransport client)
+ public void query(HttpRequestFactory factory)
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;
+ String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
+ InputStreamContent isc = new InputStreamContent(null, inputStream );
+
+ HttpRequest request = factory.buildPostRequest(url, isc);
+
+ GoogleHeaders headers = new GoogleHeaders();
+ headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
+ headers.gdataVersion = GDATA_VERSION;
+ headers.setGoogleLogin(auth.getAuthToken());
+ headers.setContentType(CONTENT_TYPE);
+ request.setHeaders(headers);
+
Log.d(Constants.TAG, "Running update query " + url.toString() + ": " + sql);
HttpResponse response;
try {
response = request.execute();
} catch (HttpResponseException e) {
- throw new GDataWrapper.HttpException(e.response.statusCode, e.response.statusMessage);
+ throw new GDataWrapper.HttpException(e.getResponse().getStatusCode(),
+ e.getResponse().getStatusMessage());
}
- boolean success = response.isSuccessStatusCode;
+ boolean success = response.isSuccessStatusCode();
if (success) {
byte[] result = new byte[1024];
int read = response.getContent().read(result);
@@ -704,9 +700,11 @@ public class SendToFusionTables implements Runnable {
Log.w(Constants.TAG, "Unrecognized response: " + lines[0]);
}
} else {
- Log.d(Constants.TAG, "Query failed: " + response.statusMessage + " (" +
- response.statusCode + ")");
- throw new GDataWrapper.HttpException(response.statusCode, response.statusMessage);
+ Log.d(Constants.TAG,
+ "Query failed: " + response.getStatusMessage() + " ("
+ + response.getStatusCode() + ")");
+ throw new GDataWrapper.HttpException(
+ response.getStatusCode(), response.getStatusMessage());
}
}
});
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java
index 51e9439ea..3162c4779 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java
@@ -15,63 +15,13 @@
*/
package com.google.android.apps.mytracks.io.backup;
-import com.google.android.apps.mytracks.util.ApiFeatures;
-
-import android.app.backup.BackupManager;
-import android.content.Context;
-import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
/**
* Shared preferences listener which notifies the backup system about new data
* being available for backup.
- * This class is API-version-safe and will provide a dummy implementation if
- * the device doesn't support backup services.
*
* @author Rodrigo Damazio
*/
-public abstract class BackupPreferencesListener
- implements OnSharedPreferenceChangeListener {
-
- /**
- * Real implementation of the listener, which calls the {@link BackupManager}.
- */
- private static class BackupPreferencesListenerImpl
- extends BackupPreferencesListener {
- private final BackupManager backupManager;
-
- public BackupPreferencesListenerImpl(Context context) {
- this.backupManager = new BackupManager(context);
- }
-
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, String key) {
- backupManager.dataChanged();
- }
- }
-
- /**
- * Dummy implementation of the listener which does nothing.
- */
- private static class DummyBackupPreferencesListener
- extends BackupPreferencesListener {
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, String key) {
- // Do nothing
- }
- }
-
- /**
- * Creates and returns a proper instance of the listener for this device.
- */
- public static BackupPreferencesListener create(
- Context context, ApiFeatures apiFeatures) {
- if (apiFeatures.hasBackup()) {
- return new BackupPreferencesListenerImpl(context);
- } else {
- return new DummyBackupPreferencesListener();
- }
- }
+public interface BackupPreferencesListener extends OnSharedPreferenceChangeListener {
}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListenerImpl.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListenerImpl.java
new file mode 100644
index 000000000..5f11b2876
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListenerImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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.backup;
+
+import android.app.backup.BackupManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Implementation of {@link BackupPreferencesListener} that calls the
+ * {@link BackupManager}.
+ *
+ * @author Jimmy Shih
+ */
+public class BackupPreferencesListenerImpl implements BackupPreferencesListener {
+
+ private final BackupManager backupManager;
+
+ public BackupPreferencesListenerImpl(Context context) {
+ this.backupManager = new BackupManager(context);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ backupManager.dataChanged();
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsHelper.java b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsHelper.java
index ca02b7aec..101fd9256 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsHelper.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsHelper.java
@@ -34,6 +34,7 @@ import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.content.Context;
+import android.text.format.DateFormat;
import android.util.Log;
import java.io.BufferedReader;
@@ -44,7 +45,6 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
-import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
@@ -287,13 +287,16 @@ public class DocsHelper {
String elevationUnit = context.getString(metricUnits ?
R.string.meter : R.string.feet);
- DateFormat trackDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
-
+ java.text.DateFormat dateFormat = DateFormat.getDateFormat(context);
+ java.text.DateFormat timeFormat = DateFormat.getTimeFormat(context);
+ Date startTime = new Date(stats.getStartTime());
+ String dateString = dateFormat.format(startTime) + " " + timeFormat.format(startTime);
+
// Prepare the Post-Text we are going to send.
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
.append("name", track.getName())
.append("description", track.getDescription())
- .append("date", trackDateFormat.format(new Date(stats.getStartTime())))
+ .append("date", dateString)
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
stats.getTotalTime()))
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
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
index b20ae2eb6..f0cf5180f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/docs/DocsTagBuilder.java
@@ -15,6 +15,7 @@
*/
package com.google.android.apps.mytracks.io.docs;
+import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
@@ -45,8 +46,9 @@ import java.util.Locale;
* @author Matthew Simmons
*/
class DocsTagBuilder {
+
private static final DecimalFormatSymbols FORMAT_SYMBOLS =
- DecimalFormatSymbols.getInstance(Locale.ENGLISH);
+ ApiFeatures.getInstance().getApiAdapter().getDecimalFormatSymbols(Locale.ENGLISH);
private static final NumberFormat LARGE_UNIT_FORMAT =
new DecimalFormat("#,###,###.00", FORMAT_SYMBOLS);
private static final NumberFormat SMALL_UNIT_FORMAT =
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
index f41930fd8..c759e24d9 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java
@@ -70,6 +70,11 @@ class WriteProgressController {
public void setOnCompletionListener(OnCompletionListener onCompletionListener) {
this.onCompletionListener = onCompletionListener;
}
+
+ // For testing purpose
+ OnCompletionListener getOnCompletionListener() {
+ return onCompletionListener;
+ }
public ProgressDialog createProgressDialog() {
dialog = new ProgressDialog(activity);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathDescriptor.java b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathDescriptor.java
new file mode 100644
index 000000000..f6fc968d5
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathDescriptor.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2011 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.maps;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.Track;
+import com.google.android.apps.mytracks.stats.TripStatistics;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.util.Log;
+
+
+/**
+ * A dynamic speed path descriptor.
+ *
+ * @author Vangelis S.
+ */
+public class DynamicSpeedTrackPathDescriptor
+ implements TrackPathDescriptor, OnSharedPreferenceChangeListener {
+ private int slowSpeed;
+ private int normalSpeed;
+ private int speedMargin;
+ private double averageMovingSpeed;
+ private final Context context;
+
+ public DynamicSpeedTrackPathDescriptor(Context context){
+ this.context = context;
+ SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+
+ if (prefs == null) {
+ speedMargin = 25;
+ return;
+ }
+ prefs.registerOnSharedPreferenceChangeListener(this);
+
+ speedMargin = Integer.parseInt(prefs.getString(
+ context.getString(R.string.track_color_mode_dynamic_speed_variation_key), "25"));
+ }
+
+ /**
+ * Get the slow speed calculated based on the % below the average speed.
+ * @return The speed limit considered as slow.
+ */
+ public int getSlowSpeed()
+ {
+ slowSpeed = (int) (averageMovingSpeed - (averageMovingSpeed * speedMargin / 100));
+ return slowSpeed;
+ }
+
+ /**
+ * Get the medium speed calculated based on the % above the average speed.
+ * @return The speed limit considered as normal.
+ */
+ public int getNormalSpeed()
+ {
+ normalSpeed = (int) (averageMovingSpeed + (averageMovingSpeed * speedMargin / 100));
+ return normalSpeed;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Log.d(TAG, "DynamicSpeedTrackPathDescriptor: onSharedPreferences changed " + key);
+ if (key == null
+ || !key.equals(context.getString(R.string.track_color_mode_dynamic_speed_variation_key))) {
+ return;
+ }
+ SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+
+ if (prefs == null) {
+ speedMargin = 25;
+ return;
+ }
+
+ speedMargin =
+ Integer.parseInt(
+ prefs.getString(
+ context.getString(R.string.track_color_mode_dynamic_speed_variation_key), "25"));
+ }
+
+ @Override
+ public boolean needsRedraw() {
+ SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ long currentTrackId = prefs.getLong(context.getString(R.string.selected_track_key), -1);
+ if(currentTrackId == -1) {
+ // Could not find track.
+ return false;
+ }
+ Track track = MyTracksProviderUtils.Factory.get(context).getTrack(currentTrackId);
+ TripStatistics stats = track.getStatistics();
+ double newaverageSpeed = (int) Math.floor(stats.getAverageMovingSpeed() * 3.6);
+
+ if(averageMovingSpeed == 0) {
+ averageMovingSpeed = newaverageSpeed;
+ return true;
+ }
+
+ double difference = Math.max(averageMovingSpeed, newaverageSpeed);
+
+ if (difference == 0.0) {
+ difference = 0.0;
+ } else {
+ difference = Math.abs(averageMovingSpeed - newaverageSpeed) / difference * 100;
+ }
+ if(difference >= 20) {
+ averageMovingSpeed = newaverageSpeed;
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java
new file mode 100644
index 000000000..2234f8b8f
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/DynamicSpeedTrackPathPainter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2011 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.maps;
+
+import com.google.android.apps.mytracks.ColoredPath;
+import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
+import com.google.android.maps.GeoPoint;
+import com.google.android.maps.Projection;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A path painter that varies the path colors based on fixed speeds or average speed margin
+ * depending of the TrackPathDescriptor passed to its constructor.
+ *
+ * @author Vangelis S.
+ */
+public class DynamicSpeedTrackPathPainter implements TrackPathPainter {
+ private final Paint selectedTrackPaintSlow;
+ private final Paint selectedTrackPaintMedium;
+ private final Paint selectedTrackPaintFast;
+ private final List coloredPaths;
+ private final TrackPathDescriptor trackPathDescriptor;
+ private int slowSpeed;
+ private int normalSpeed;
+
+ public DynamicSpeedTrackPathPainter (Context context, TrackPathDescriptor trackPathDescriptor) {
+ this.trackPathDescriptor = trackPathDescriptor;
+
+ selectedTrackPaintSlow = TrackPathUtilities.getPaint(R.color.slow_path, context);
+ selectedTrackPaintMedium = TrackPathUtilities.getPaint(R.color.normal_path, context);
+ selectedTrackPaintFast = TrackPathUtilities.getPaint(R.color.fast_path, context);
+
+ this.coloredPaths = new ArrayList();
+ }
+
+ @Override
+ public void drawTrack(Canvas canvas) {
+ for(int i = 0; i < coloredPaths.size(); ++i) {
+ ColoredPath coloredPath = coloredPaths.get(i);
+ canvas.drawPath(coloredPath.getPath(), coloredPath.getPathPaint());
+ }
+ }
+
+ @Override
+ public void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
+ Boolean alwaysVisible, List points) {
+ // Whether to start a new segment on new valid and visible point.
+ boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid;
+ boolean lastVisible = !newSegment;
+ final Point pt = new Point();
+
+ clear();
+
+ slowSpeed = trackPathDescriptor.getSlowSpeed();
+ normalSpeed = trackPathDescriptor.getNormalSpeed();
+
+ // Loop over track points.
+ for (int i = startLocationIdx; i < points.size(); ++i) {
+ CachedLocation loc = points.get(i);
+
+ // Check if valid, if not then indicate a new segment.
+ if (!loc.valid) {
+ newSegment = true;
+ continue;
+ }
+
+ final GeoPoint geoPoint = loc.geoPoint;
+ // Check if this breaks the existing segment.
+ boolean visible = alwaysVisible || viewRect.contains(
+ geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
+ if (!visible && !lastVisible) {
+ // This is a point outside view not connected to a visible one.
+ newSegment = true;
+ }
+ lastVisible = visible;
+
+ // Either move to beginning of a new segment or continue the old one.
+ if (newSegment) {
+ projection.toPixels(geoPoint, pt);
+ newSegment = false;
+ } else {
+ ColoredPath coloredPath;
+ if(loc.speed <= slowSpeed) {
+ coloredPath = new ColoredPath(selectedTrackPaintSlow);
+ }
+ else if(loc.speed <= normalSpeed) {
+ coloredPath = new ColoredPath(selectedTrackPaintMedium);
+ } else {
+ coloredPath = new ColoredPath(selectedTrackPaintFast);
+ }
+ coloredPath.getPath().moveTo(pt.x, pt.y);
+ projection.toPixels(geoPoint, pt);
+ coloredPath.getPath().lineTo(pt.x, pt.y);
+ coloredPaths.add(coloredPath);
+ }
+ }
+ }
+
+ @Override
+ public void clear()
+ {
+ coloredPaths.clear();
+ }
+
+ @Override
+ public boolean needsRedraw() {
+ return trackPathDescriptor.needsRedraw();
+ }
+
+ @Override
+ public Path getLastPath() {
+ Path path = new Path();
+ for(int i = 0; i < coloredPaths.size(); ++i) {
+ path.addPath(coloredPaths.get(i).getPath());
+ }
+ return path;
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/FixedSpeedTrackPathDescriptor.java b/MyTracks/src/com/google/android/apps/mytracks/maps/FixedSpeedTrackPathDescriptor.java
new file mode 100644
index 000000000..e69b4a6a5
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/FixedSpeedTrackPathDescriptor.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2011 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.maps;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.util.Log;
+
+
+/**
+ * A fixed speed path descriptor.
+ *
+ * @author Vangelis S.
+ */
+public class FixedSpeedTrackPathDescriptor implements TrackPathDescriptor, OnSharedPreferenceChangeListener {
+ private int slowSpeed;
+ private int normalSpeed;
+ private final Context context;
+
+ public FixedSpeedTrackPathDescriptor(Context context) {
+
+ this.context = context;
+ context.getSharedPreferences(Constants.SETTINGS_NAME, 0)
+ .registerOnSharedPreferenceChangeListener(this);
+
+ SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ if (prefs == null) {
+ slowSpeed = 9;
+ normalSpeed = 17;
+ return;
+ }
+ try {
+ slowSpeed = Integer.parseInt(prefs.getString(context.getString(
+ R.string.track_color_mode_fixed_speed_slow_key), "9"));
+ } catch (NumberFormatException e) {
+ slowSpeed = 9;
+ }
+
+ try {
+ normalSpeed = Integer.parseInt(prefs.getString(context.getString(
+ R.string.track_color_mode_fixed_speed_medium_key), "17"));
+ } catch (NumberFormatException e) {
+ normalSpeed = 17;
+ }
+ }
+
+ /**
+ * Gets the slow speed for reference.
+ * @return The speed limit considered as slow.
+ */
+ public int getSlowSpeed() {
+ return slowSpeed;
+ }
+
+ /**
+ * Gets the normal speed for reference.
+ * @return The speed limit considered as normal.
+ */
+ public int getNormalSpeed() {
+ return normalSpeed;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ Log.d(TAG, "FixedSpeedTrackPathDescriptor: onSharedPreferences changed " + key);
+ if (key == null
+ || (!key.equals(context.getString(R.string.track_color_mode_fixed_speed_slow_key))
+ && !key.equals(context.getString(R.string.track_color_mode_fixed_speed_medium_key)))) {
+ return;
+ }
+ SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ if (prefs == null) {
+ slowSpeed = 9;
+ normalSpeed = 17;
+ return;
+ }
+
+ try {
+ slowSpeed = Integer.parseInt(prefs.getString(context.getString(
+ R.string.track_color_mode_fixed_speed_slow_key), "9"));
+ } catch (NumberFormatException e) {
+ slowSpeed = 9;
+ }
+ try {
+ normalSpeed = Integer.parseInt(prefs.getString(context.getString(
+ R.string.track_color_mode_fixed_speed_medium_key), "17"));
+ } catch (NumberFormatException e) {
+ normalSpeed = 17;
+ }
+ }
+
+ @Override
+ public boolean needsRedraw() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java
new file mode 100644
index 000000000..c73927cd6
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/SingleColorTrackPathPainter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2011 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.maps;
+
+import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
+import com.google.android.maps.GeoPoint;
+import com.google.android.maps.Projection;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import java.util.List;
+
+
+/**
+ * A path painter that not variates the path colors.
+ *
+ * @author Vangelis S.
+ */
+public class SingleColorTrackPathPainter implements TrackPathPainter {
+ private final Paint selectedTrackPaint;
+ private Path path;
+
+ public SingleColorTrackPathPainter(Context context) {
+ selectedTrackPaint = TrackPathUtilities.getPaint(R.color.red, context);
+ }
+
+ @Override
+ public void drawTrack(Canvas canvas) {
+ canvas.drawPath(path, selectedTrackPaint);
+ }
+
+ @Override
+ public void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
+ Boolean alwaysVisible, List points) {
+ // Whether to start a new segment on new valid and visible point.
+ boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid;
+ boolean lastVisible = !newSegment;
+ final Point pt = new Point();
+ // Loop over track points.
+ int numPoints = points.size();
+ path = newPath();
+ path.incReserve(numPoints);
+
+ for (int i = startLocationIdx; i < numPoints ; ++i) {
+ CachedLocation loc = points.get(i);
+
+ // Check if valid, if not then indicate a new segment.
+ if (!loc.valid) {
+ newSegment = true;
+ continue;
+ }
+
+ final GeoPoint geoPoint = loc.geoPoint;
+ // Check if this breaks the existing segment.
+ boolean visible = alwaysVisible
+ || viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
+ if (!visible && !lastVisible) {
+ // This is a point outside view not connected to a visible one.
+ newSegment = true;
+ }
+ lastVisible = visible;
+
+ // Either move to beginning of a new segment or continue the old one.
+ projection.toPixels(geoPoint, pt);
+ if (newSegment) {
+ path.moveTo(pt.x, pt.y);
+ newSegment = false;
+ } else {
+ path.lineTo(pt.x, pt.y);
+ }
+ }
+ }
+
+ @Override
+ public void clear() {
+ path = null;
+ }
+
+ @Override
+ public boolean needsRedraw() {
+ return false;
+ }
+
+ @Override
+ public Path getLastPath() {
+ return path;
+ }
+
+ // Visible for testing
+ public Path newPath() {
+ return new Path();
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathDescriptor.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathDescriptor.java
new file mode 100644
index 000000000..389994e6b
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathDescriptor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2011 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.maps;
+
+/**
+ * An interface for classes which describe how to draw a track path.
+ *
+ * @author Vangelis S.
+ */
+public interface TrackPathDescriptor {
+ /**
+ * @return The maximum speed which is considered slow.
+ */
+ int getSlowSpeed();
+
+ /**
+ * @return The maximum speed which is considered normal.
+ */
+ int getNormalSpeed();
+
+ /**
+ * @return True if the path needs to be updated.
+ */
+ boolean needsRedraw();
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java
new file mode 100644
index 000000000..1f1d8e31d
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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.maps;
+
+import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
+import com.google.android.maps.Projection;
+
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Rect;
+
+import java.util.List;
+
+/**
+ * An interface for classes which paint the track path.
+ *
+ * @author Vangelis S.
+ */
+public interface TrackPathPainter {
+
+ /**
+ * Clears the related data.
+ */
+ void clear();
+
+ /**
+ * Draws the path to the canvas.
+ * @param canvas The Canvas to draw upon
+ */
+ void drawTrack(Canvas canvas);
+
+ /**
+ * Updates the path.
+ * @param projection The Canvas to draw upon.
+ * @param viewRect The Path to be drawn.
+ * @param startLocationIdx The start point from where update the path.
+ * @param alwaysVisible Flag for alwaysvisible.
+ * @param points The list of points used to update the path.
+ */
+ void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
+ Boolean alwaysVisible, List points);
+
+ /**
+ * @return True if the path needs to be updated.
+ */
+ boolean needsRedraw();
+
+ /**
+ * @return The path being used currently.
+ */
+ Path getLastPath();
+}
+
+
+
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java
new file mode 100644
index 000000000..14f8f29cd
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathPainterFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011 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.maps;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.Constants;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ * A factory for TrackPathPainters.
+ *
+ * @author Vangelis S.
+ */
+public class TrackPathPainterFactory {
+
+ private TrackPathPainterFactory() {
+ }
+
+ /**
+ * Get a new TrackPathPainter.
+ * @param context Context to fetch system preferences.
+ * @return The TrackPathPainter that corresponds to the track color mode setting.
+ */
+ public static TrackPathPainter getTrackPathPainter(Context context) {
+ SharedPreferences prefs =
+ context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ if (prefs == null) {
+ return new SingleColorTrackPathPainter(context);
+ }
+
+ String colorMode = prefs.getString(context.getString(R.string.track_color_mode_key), null);
+ Log.i(TAG, "Creating track path painter of type: " + colorMode);
+
+ if (colorMode == null || colorMode.equals(context.getString(R.string.track_color_mode_none))) {
+ return new SingleColorTrackPathPainter(context);
+ } else if (colorMode.equals(context.getString(R.string.track_color_mode_fixed))) {
+ return new DynamicSpeedTrackPathPainter(context,
+ new FixedSpeedTrackPathDescriptor(context));
+ } else if (colorMode.equals(context.getString(R.string.track_color_mode_dynamic))) {
+ return new DynamicSpeedTrackPathPainter(context,
+ new DynamicSpeedTrackPathDescriptor(context));
+ } else {
+ Log.w(TAG, "Using default track path painter. Unrecognized painter: " + colorMode);
+ return new SingleColorTrackPathPainter(context);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtilities.java b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtilities.java
new file mode 100644
index 000000000..956d2c8eb
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/maps/TrackPathUtilities.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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.maps;
+
+import android.content.Context;
+import android.graphics.Paint;
+
+/**
+ * Various utility functions for TrackPath painting.
+ *
+ * @author Vangelis S.
+ */
+public class TrackPathUtilities {
+
+ public static Paint getPaint(int id, Context context) {
+ Paint paint = new Paint();
+ paint.setColor(context.getResources().getColor(id));
+ paint.setStrokeWidth(3);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setAntiAlias(true);
+ return paint;
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java
new file mode 100644
index 000000000..8c8f3cc70
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/ControlRecordingService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.android.apps.mytracks.services;
+
+import static com.google.android.apps.mytracks.Constants.TAG;
+
+import com.google.android.apps.mytracks.widgets.TrackWidgetProvider;
+import com.google.android.maps.mytracks.R;
+
+import android.app.IntentService;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A service to control starting and stopping of a recording. This service,
+ * through the AndroidManifest.xml, is configured to only allow components of
+ * the same application to invoke it. Thus this service can be used my MyTracks
+ * app widget, {@link TrackWidgetProvider}, but not by other applications. This
+ * application delegates starting and stopping a recording to
+ * {@link TrackRecordingService} using RPC calls.
+ *
+ * @author jshih@google.com (Jimmy Shih)
+ */
+public class ControlRecordingService extends IntentService implements ServiceConnection {
+
+ private ITrackRecordingService trackRecordingService;
+ private boolean connected = false;
+
+ public ControlRecordingService() {
+ super(ControlRecordingService.class.getSimpleName());
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Intent newIntent = new Intent(this, TrackRecordingService.class);
+ startService(newIntent);
+ bindService(newIntent, this, 0);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ trackRecordingService = ITrackRecordingService.Stub.asInterface(service);
+ notifyConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ connected = false;
+ }
+
+ /**
+ * Notifies all threads that connection to {@link TrackRecordingService} is
+ * available.
+ */
+ private synchronized void notifyConnected() {
+ connected = true;
+ notifyAll();
+ }
+
+ /**
+ * Waits until the connection to {@link TrackRecordingService} is available.
+ */
+ private synchronized void waitConnected() {
+ while (!connected) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+
+ // can safely ignore
+ }
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ waitConnected();
+ String action = intent.getAction();
+ if (action != null) {
+ try {
+ if (action.equals(getString(R.string.start_new_track_action))) {
+ trackRecordingService.startNewTrack();
+ } else if (action.equals(getString(R.string.end_current_track_action))) {
+ trackRecordingService.endCurrentTrack();
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "ControlRecordingService onHandleIntent RemoteException", e);
+ }
+ }
+ unbindService(this);
+ connected = false;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (connected) {
+ unbindService(this);
+ connected = false;
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
index ee63dca09..c32f88c10 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
@@ -37,7 +37,7 @@ import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiFeatures;
-import com.google.android.apps.mytracks.util.ApiPlatformAdapter;
+import com.google.android.apps.mytracks.util.ApiLevelAdapter;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -56,11 +56,13 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.os.Process;
import android.util.Log;
import java.util.Timer;
@@ -153,6 +155,9 @@ public class TrackRecordingService extends Service {
@Override
public void onLocationChanged(final Location location) {
+ if (executorService.isShutdown() || executorService.isTerminated()) {
+ return;
+ }
executorService.submit(
new Runnable() {
@Override
@@ -226,6 +231,13 @@ public class TrackRecordingService extends Service {
* Application lifetime events:
*/
+ /*
+ * Note that this service, through the AndroidManifest.xml, is configured to
+ * allow both MyTracks and third party apps to invoke it. For the onCreate
+ * callback, we cannot tell whether the caller is MyTracks or a third party
+ * app, thus it cannot start/stop a recording or write/update MyTracks
+ * database. However, it can resume a recording.
+ */
@Override
public void onCreate() {
super.onCreate();
@@ -265,40 +277,48 @@ public class TrackRecordingService extends Service {
showNotification();
}
+ /*
+ * Note that this service, through the AndroidManifest.xml, is configured to
+ * allow both MyTracks and third party apps to invoke it. For the onStart
+ * callback, we cannot tell whether the caller is MyTracks or a third party
+ * app, thus it cannot start/stop a recording or write/update MyTracks
+ * database. However, it can resume a recording.
+ */
@Override
public void onStart(Intent intent, int startId) {
handleStartCommand(intent, startId);
}
+ /*
+ * Note that this service, through the AndroidManifest.xml, is configured to
+ * allow both MyTracks and third party apps to invoke it. For the
+ * onStartCommand callback, we cannot tell whether the caller is MyTracks or a
+ * third party app, thus it cannot start/stop a recording or write/update
+ * MyTracks database. However, it can resume a recording.
+ */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleStartCommand(intent, startId);
return START_STICKY;
}
-
+
private void handleStartCommand(Intent intent, int startId) {
- Log.d(TAG,
- "TrackRecordingService.handleStartCommand: " + startId);
+ Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId);
+
+ if (intent == null) {
+ return;
+ }
// Check if called on phone reboot with resume intent.
- if (intent != null) {
- if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
- resumeTrack(startId);
- } else {
- // Process actions for controlling the service.
- String action = intent.getAction();
- if (getString(R.string.start_new_track_action).equals(action)) {
- startNewTrack();
- if (intent.getBooleanExtra(getString(R.string.select_new_track_extra), false)) {
- prefManager.setSelectedTrack(recordingTrackId);
- }
- } else if (getString(R.string.end_current_track_action).equals(action)) {
- endCurrentTrack();
- }
- }
+ if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
+ resumeTrack(startId);
}
}
+ private boolean isTrackInProgress() {
+ return recordingTrackId != -1 || isRecording;
+ }
+
private void resumeTrack(int startId) {
Log.d(TAG, "TrackRecordingService: requested resume");
@@ -342,7 +362,6 @@ public class TrackRecordingService extends Service {
timer.purge();
unregisterLocationListener();
shutdownTaskExecutors();
- executorService.shutdown();
if (sensorManager != null) {
sensorManager.shutdown();
sensorManager = null;
@@ -358,6 +377,8 @@ public class TrackRecordingService extends Service {
// This should be the last operation.
releaseWakeLock();
+ // Shutdown the executor service last to avoid sending events to a dead executor.
+ executorService.shutdown();
super.onDestroy();
}
@@ -464,8 +485,8 @@ public class TrackRecordingService extends Service {
* Shows the notification message and icon in the notification bar.
*/
private void showNotification() {
- final ApiPlatformAdapter apiPlatformAdapter =
- ApiFeatures.getInstance().getApiPlatformAdapter();
+ final ApiLevelAdapter apiLevelAdapter =
+ ApiFeatures.getInstance().getApiAdapter();
if (isRecording) {
Notification notification = new Notification(
R.drawable.arrow_320, null /* tickerText */,
@@ -476,10 +497,10 @@ public class TrackRecordingService extends Service {
notification.setLatestEventInfo(this, getString(R.string.app_name),
getString(R.string.recording_your_track), contentIntent);
notification.flags += Notification.FLAG_NO_CLEAR;
- apiPlatformAdapter.startForeground(this, notificationManager, 1,
+ apiLevelAdapter.startForeground(this, notificationManager, 1,
notification);
} else {
- apiPlatformAdapter.stopForeground(this, notificationManager, 1);
+ apiLevelAdapter.stopForeground(this, notificationManager, 1);
}
}
@@ -543,10 +564,10 @@ public class TrackRecordingService extends Service {
* Recording lifecycle.
*/
- public long startNewTrack() {
+ private long startNewTrack() {
Log.d(TAG, "TrackRecordingService.startNewTrack");
- if (recordingTrackId != -1 || isRecording) {
- throw new IllegalStateException("A track is already in progress!");
+ if (isTrackInProgress()) {
+ return -1L;
}
long startTime = System.currentTimeMillis();
@@ -954,8 +975,7 @@ public class TrackRecordingService extends Service {
* Build a statistics marker.
* A statistics marker holds the stats for the* last segment up to this marker.
*
- * @param Waypoint The waypoint which will be populated with stats data.
- * @return the unique id of the inserted marker
+ * @param waypoint The waypoint which will be populated with stats data.
*/
private void buildStatisticsMarker(Waypoint waypoint) {
StringUtils utils = new StringUtils(TrackRecordingService.this);
@@ -983,8 +1003,8 @@ public class TrackRecordingService extends Service {
private void endCurrentTrack() {
Log.d(TAG, "TrackRecordingService.endCurrentTrack");
- if (recordingTrackId == -1 || !isRecording) {
- throw new IllegalStateException("No recording track in progress!");
+ if (!isTrackInProgress()) {
+ return;
}
announcementExecutor.shutdown();
@@ -1025,12 +1045,15 @@ public class TrackRecordingService extends Service {
}
private void sendTrackBroadcast(int actionResId, long trackId) {
- Intent broadcastIntent =
- new Intent()
- .setAction(getString(actionResId))
- .putExtra(getString(R.string.track_id_broadcast_extra), trackId);
- sendBroadcast(broadcastIntent,
- getString(R.string.broadcast_notifications_permission));
+ Intent broadcastIntent = new Intent()
+ .setAction(getString(actionResId))
+ .putExtra(getString(R.string.track_id_broadcast_extra), trackId);
+ sendBroadcast(broadcastIntent, getString(R.string.mytracks_notifications_permission));
+
+ SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
+ if (sharedPreferences.getBoolean(getString(R.string.allow_access_key), false)) {
+ sendBroadcast(broadcastIntent, getString(R.string.broadcast_notifications_permission));
+ }
}
/*
@@ -1162,29 +1185,58 @@ public class TrackRecordingService extends Service {
}
}
+ /**
+ * Checks if the service is available. If not, throws an
+ * {@link IllegalStateException}.
+ */
private void checkService() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
}
+
+ /**
+ * Returns true if the RPC caller is from the same application or if the
+ * "Allow access" setting indicates that another app can invoke this service's
+ * RPCs.
+ */
+ private boolean canAccess() {
+
+ // As a precondition for access, must check if the service is available.
+ checkService();
+
+ if (Process.myPid() == Binder.getCallingPid()) {
+ return true;
+ } else {
+ SharedPreferences sharedPreferences = service.getSharedPreferences(
+ Constants.SETTINGS_NAME, 0);
+ return sharedPreferences.getBoolean(service.getString(R.string.allow_access_key), false);
+ }
+ }
// Service method delegates.
@Override
public boolean isRecording() {
- checkService();
+ if (!canAccess()) {
+ return false;
+ }
return service.isRecording();
}
@Override
public long getRecordingTrackId() {
- checkService();
+ if (!canAccess()) {
+ return -1L;
+ }
return service.recordingTrackId;
}
@Override
public long startNewTrack() {
- checkService();
+ if (!canAccess()) {
+ return -1L;
+ }
return service.startNewTrack();
}
@@ -1195,25 +1247,33 @@ public class TrackRecordingService extends Service {
* @return the unique ID of the inserted marker
*/
public long insertWaypoint(WaypointCreationRequest request) {
- checkService();
+ if (!canAccess()) {
+ return -1L;
+ }
return service.insertWaypoint(request);
}
@Override
public void endCurrentTrack() {
- checkService();
+ if (!canAccess()) {
+ return;
+ }
service.endCurrentTrack();
}
@Override
public void recordLocation(Location loc) {
- checkService();
+ if (!canAccess()) {
+ return;
+ }
service.locationListener.onLocationChanged(loc);
}
@Override
public byte[] getSensorData() {
- checkService();
+ if (!canAccess()) {
+ return null;
+ }
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return null;
@@ -1227,7 +1287,9 @@ public class TrackRecordingService extends Service {
@Override
public int getSensorState() {
- checkService();
+ if (!canAccess()) {
+ return Sensor.SensorState.NONE.getNumber();
+ }
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber();
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
index 8fbbdc44b..9e1f59fe9 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
+import com.google.android.apps.mytracks.util.ApiFeatures;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -261,22 +262,29 @@ public class BluetoothConnectionManager {
}
private BluetoothSocket getSocket() throws IOException {
- try {
- Class extends BluetoothDevice> c = device.getClass();
- Method insecure = c.getMethod("createInsecureRfcommSocket",
- Integer.class);
- insecure.setAccessible(true);
- return (BluetoothSocket) insecure.invoke(device, 1);
- } catch (SecurityException e) {
- Log.e(Constants.TAG, "Unable to get insecure connect.", e);
- } catch (NoSuchMethodException e) {
- Log.e(Constants.TAG, "Unable to get insecure connect.", e);
- } catch (IllegalArgumentException e) {
- Log.e(Constants.TAG, "Unable to get insecure connect.", e);
- } catch (IllegalAccessException e) {
- Log.e(Constants.TAG, "Unable to get insecure connect.", e);
- } catch (InvocationTargetException e) {
- Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ if (ApiFeatures.getInstance().hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord()) {
+ try {
+ return device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
+ } catch (IOException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ }
+ } else {
+ try {
+ Class extends BluetoothDevice> c = device.getClass();
+ Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
+ insecure.setAccessible(true);
+ return (BluetoothSocket) insecure.invoke(device, 1);
+ } catch (SecurityException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ } catch (NoSuchMethodException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ } catch (IllegalArgumentException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ } catch (IllegalAccessException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ } catch (InvocationTargetException e) {
+ Log.e(Constants.TAG, "Unable to get insecure connect.", e);
+ }
}
return device.createRfcommSocketToServiceRecord(SPP_UUID);
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java
index 41fed2e52..7e3a292b8 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.services.sensors;
+import static com.google.android.apps.mytracks.Constants.TAG;
+
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
@@ -24,10 +26,13 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
+import java.util.ArrayList;
+
/**
* Manage the connection to a bluetooth sensor.
*
@@ -36,8 +41,8 @@ import android.widget.Toast;
public class BluetoothSensorManager extends SensorManager {
// Local Bluetooth adapter
- private BluetoothAdapter bluetoothAdapter = null;
-
+ private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
+
// Member object for the sensor threads and connections.
private BluetoothConnectionManager connectionManager = null;
@@ -54,8 +59,6 @@ public class BluetoothSensorManager extends SensorManager {
Context context, MessageParser parser) {
this.context = context;
this.parser = parser;
- // Get local Bluetooth adapter
- bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// If BT is not available or not enabled quit.
if (!isEnabled()) {
return;
@@ -71,6 +74,50 @@ public class BluetoothSensorManager extends SensorManager {
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
}
+ /**
+ * Code for assigning the local bluetooth adapter
+ *
+ * @return The default bluetooth adapter, if one is available, NULL if it isn't.
+ */
+ private static BluetoothAdapter getDefaultBluetoothAdapter() {
+ // Check if the calling thread is the main application thread,
+ // if it is, do it directly.
+ if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
+
+ // If the calling thread, isn't the main application thread,
+ // then get the main application thread to return the default adapter.
+ final ArrayList adapters = new ArrayList(1);
+ final Object mutex = new Object();
+
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ adapters.add(BluetoothAdapter.getDefaultAdapter());
+ synchronized (mutex) {
+ mutex.notify();
+ }
+ }
+ });
+
+ while (adapters.isEmpty()) {
+ synchronized (mutex) {
+ try {
+ mutex.wait(1000L);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
+ }
+ }
+ }
+
+ if (adapters.get(0) == null) {
+ Log.w(TAG, "No bluetooth adapter found!");
+ }
+ return adapters.get(0);
+ }
+
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
@@ -125,7 +172,7 @@ public class BluetoothSensorManager extends SensorManager {
}
// The Handler that gets information back from the BluetoothSensorService
- private final Handler messageHandler = new Handler() {
+ private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java
index 24d93c2fd..b70e0fddf 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
+import com.google.android.apps.mytracks.util.ApiFeatures;
import java.util.Arrays;
@@ -23,7 +24,7 @@ import java.util.Arrays;
* An implementation of a Sensor MessageParser for Zephyr.
*
* @author Sandor Dornbush
- * @author Dominik Ršttsches
+ * @author Dominik Rttsches
*/
public class ZephyrMessageParser implements MessageParser {
@@ -61,8 +62,8 @@ public class ZephyrMessageParser implements MessageParser {
// 0x1A00316550003162 produces erroneous values for Cadence and needs
// a workaround based on the stride counter.
// Firmware values range from field 3 to 10 (inclusive) of the byte buffer.
- byte[] hardwareFirmwareId = Arrays.copyOfRange(buffer, 3, 11);
-
+ byte[] hardwareFirmwareId = ApiFeatures.getInstance().getApiAdapter()
+ .copyByteArray(buffer, 3, 11);
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) {
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorManager.java
index d8a37a55d..096943abd 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ant/AntSensorManager.java
@@ -22,6 +22,7 @@ import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
+import com.dsi.ant.exception.AntServiceNotConnectedException;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
@@ -142,13 +143,16 @@ public abstract class AntSensorManager extends SensorManager {
Log.w(TAG, "Failed to unregister ANT data receiver", e);
}
- try {
- antReceiver.releaseInterface();
- } catch (AntInterfaceException e) {
- Log.e(TAG, "failed to release ANT interface", e);
+ if (antReceiver != null) {
+ try {
+ antReceiver.releaseInterface();
+ } catch (AntServiceNotConnectedException e) {
+ Log.i(TAG, "ANT service not connected", e);
+ } catch (AntInterfaceException e) {
+ Log.e(TAG, "failed to release ANT interface", e);
+ }
+ antReceiver.destroy();
}
-
- antReceiver.destroy();
}
@Override
@@ -236,7 +240,7 @@ public abstract class AntSensorManager extends SensorManager {
/**
* Process a raw ANT message.
* @param antMessage the ANT message, including the size and message ID bytes
- * @deprecated Use {@link #handleMessage(int, byte[])} instead.
+ * @deprecated Use {@link #handleMessage(byte, byte[])} instead.
*/
protected void handleMessage(byte[] antMessage) {
int len = antMessage[0];
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
index 30c87d781..739b81cb3 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/StatusAnnouncerFactory.java
@@ -37,11 +37,7 @@ public class StatusAnnouncerFactory implements PeriodicTaskFactory {
@Override
public PeriodicTask create(Context context) {
if (hasTts) {
- if (ApiFeatures.getInstance().isAudioFocusSupported()) {
- return new FroyoStatusAnnouncerTask(context);
- } else {
- return new StatusAnnouncerTask(context);
- }
+ return ApiFeatures.getInstance().getApiAdapter().getStatusAnnouncerTask(context);
} else {
return null;
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
index 04e88baa4..700b5a2a6 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
@@ -29,17 +29,17 @@ import android.util.Log;
public class ApiFeatures {
/**
- * The API level of the Android version we're being run under.
+ * The API level of the Android version we are being run under.
*/
- public static final int ANDROID_API_LEVEL = Integer.parseInt(
+ private static final int ANDROID_API_LEVEL = Integer.parseInt(
Build.VERSION.SDK);
private static ApiFeatures instance;
/**
- * The API platform adapter supported by this system.
+ * The API level adapter for the Android version we are being run under.
*/
- private ApiPlatformAdapter apiPlatformAdapter;
+ private ApiLevelAdapter apiLevelAdapter;
/**
* Returns the singleton instance of this class.
@@ -66,52 +66,33 @@ public class ApiFeatures {
// It is safe to import unsupported classes as long as we only actually
// load the class when supported.
if (getApiLevel() >= 9) {
- apiPlatformAdapter = new GingerbreadPlatformAdapter();
+ apiLevelAdapter = new ApiLevel9Adapter();
+ } else if (getApiLevel() >= 8) {
+ apiLevelAdapter = new ApiLevel8Adapter();
} else if (getApiLevel() >= 5) {
- apiPlatformAdapter = new EclairPlatformAdapter();
+ apiLevelAdapter = new ApiLevel5Adapter();
} else {
- apiPlatformAdapter = new CupcakePlatformAdapter();
+ apiLevelAdapter = new ApiLevel3Adapter();
}
- Log.i(Constants.TAG, "Using platform adapter " + apiPlatformAdapter.getClass());
+ Log.i(Constants.TAG, "Using API level adapter " + apiLevelAdapter.getClass());
}
- public ApiPlatformAdapter getApiPlatformAdapter() {
- return apiPlatformAdapter;
- }
-
- /**
- * Returns whether cloud backup (a.k.a. Froyo backup) is available.
- */
- public boolean hasBackup() {
- return getApiLevel() >= 8;
+ public ApiLevelAdapter getApiAdapter() {
+ return apiLevelAdapter;
}
+ // API Level 4 Changes
+
/**
* Returns whether text-to-speech is available.
*/
public boolean hasTextToSpeech() {
- if (getApiLevel() < 4) return false;
-
- try {
- Class.forName("android.speech.tts.TextToSpeech");
- } catch (ClassNotFoundException ex) {
- return false;
- } catch (LinkageError er) {
- return false;
- }
-
- return true;
- }
-
- public boolean hasStrictMode() {
- return getApiLevel() >= 9;
- }
-
- public boolean isAudioFocusSupported() {
- return getApiLevel() >= 8;
+ return getApiLevel() >= 4;
}
+ // API Level 5 Changes
+
/**
* There's a bug (#1587) in Cupcake and Donut which prevents you from
* using a SQLiteQueryBuilder twice. That is, if you call buildQuery
@@ -121,9 +102,19 @@ public class ApiFeatures {
* up. Specifically, it'll add extra parens which don't belong.
*/
public boolean canReuseSQLiteQueryBuilder() {
- return getApiLevel() > 4;
+ return getApiLevel() >= 5;
}
-
+
+ // API Level 10 changes
+
+ /**
+ * Returns true if BluetoothDevice.createInsecureRfcommSocketToServiceRecord
+ * is available.
+ */
+ public boolean hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord() {
+ return getApiLevel() >= 10;
+ }
+
// Visible for testing.
protected int getApiLevel() {
return ANDROID_API_LEVEL;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java
similarity index 62%
rename from MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
rename to MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java
index fe6ec0b47..c93b3dda3 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/CupcakePlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel3Adapter.java
@@ -17,22 +17,31 @@ package com.google.android.apps.mytracks.util;
import static com.google.android.apps.mytracks.Constants.TAG;
+import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
+import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
+import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerTask;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.apache.ApacheHttpTransport;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
/**
- * The Cupcake (API level 3) specific implementation of the
- * {@link ApiPlatformAdapter}.
- *
+ * API level 3 specific implementation of the {@link ApiLevelAdapter}.
+ *
* @author Bartlomiej Niechwiej
*/
-public class CupcakePlatformAdapter implements ApiPlatformAdapter {
+public class ApiLevel3Adapter implements ApiLevelAdapter {
@Override
public void startForeground(Service service,
@@ -51,6 +60,21 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
notificationManager.cancel(id);
}
}
+
+ @Override
+ public PeriodicTask getStatusAnnouncerTask(Context context) {
+ return new StatusAnnouncerTask(context);
+ }
+
+ @Override
+ public BackupPreferencesListener getBackupPreferencesListener(Context context) {
+ return new BackupPreferencesListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ // Do nothing
+ }
+ };
+ }
private void setServiceForeground(Service service, boolean foreground) {
// setForeground has been completely removed in API level 11, so we use reflection.
@@ -79,4 +103,22 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
public void enableStrictMode() {
// Not supported
}
+
+ @Override
+ public byte[] copyByteArray(byte[] input, int start, int end) {
+ int length = end - start;
+ byte[] output = new byte[length];
+ System.arraycopy(input, start, output, 0, length);
+ return output;
+ }
+
+ @Override
+ public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
+ return new DecimalFormatSymbols(locale);
+ }
+
+ @Override
+ public HttpTransport getHttpTransport() {
+ return new ApacheHttpTransport();
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java
similarity index 75%
rename from MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
rename to MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java
index 55540e1a4..0f963f70f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/EclairPlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel5Adapter.java
@@ -18,15 +18,13 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
-import android.content.SharedPreferences.Editor;
/**
- * The Eclair (API level 5) specific implementation of the
- * {@link ApiPlatformAdapter}.
- *
+ * API level 5 specific implementation of the {@link ApiLevelAdapter}.
+ *
* @author Bartlomiej Niechwiej
*/
-public class EclairPlatformAdapter implements ApiPlatformAdapter {
+public class ApiLevel5Adapter extends ApiLevel3Adapter {
@Override
public void startForeground(Service service,
@@ -40,14 +38,4 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
NotificationManager notificationManager, int id) {
service.stopForeground(id != -1);
}
-
- @Override
- public void applyPreferenceChanges(Editor editor) {
- editor.commit();
- }
-
- @Override
- public void enableStrictMode() {
- // Not supported
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java
new file mode 100644
index 000000000..35696189d
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel8Adapter.java
@@ -0,0 +1,26 @@
+package com.google.android.apps.mytracks.util;
+
+import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
+import com.google.android.apps.mytracks.io.backup.BackupPreferencesListenerImpl;
+import com.google.android.apps.mytracks.services.tasks.FroyoStatusAnnouncerTask;
+import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
+
+import android.content.Context;
+
+/**
+ * API level 8 specific implementation of the {@link ApiLevelAdapter}.
+ *
+ * @author Jimmy Shih
+ */
+public class ApiLevel8Adapter extends ApiLevel5Adapter {
+
+ @Override
+ public PeriodicTask getStatusAnnouncerTask(Context context) {
+ return new FroyoStatusAnnouncerTask(context);
+ }
+
+ @Override
+ public BackupPreferencesListener getBackupPreferencesListener(Context context) {
+ return new BackupPreferencesListenerImpl(context);
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/GingerbreadPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java
similarity index 51%
rename from MyTracks/src/com/google/android/apps/mytracks/util/GingerbreadPlatformAdapter.java
rename to MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java
index 35d7eb3d6..011d7d74a 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/GingerbreadPlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevel9Adapter.java
@@ -1,18 +1,24 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.javanet.NetHttpTransport;
import android.content.SharedPreferences.Editor;
import android.os.StrictMode;
import android.util.Log;
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Locale;
+
/**
- * The Gingerbread (API level 9) specific implememntation of the
- * {@link ApiPlatformAdapter}.
+ * API level 9 specific implementation of the {@link ApiLevelAdapter}.
*
* @author Rodrigo Damazio
*/
-public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
+public class ApiLevel9Adapter extends ApiLevel8Adapter {
+
@Override
public void applyPreferenceChanges(Editor editor) {
// Apply asynchronously
@@ -32,4 +38,19 @@ public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
.penaltyLog()
.build());
}
+
+ @Override
+ public byte[] copyByteArray(byte[] input, int start, int end) {
+ return Arrays.copyOfRange(input, start, end);
+ }
+
+ @Override
+ public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
+ return DecimalFormatSymbols.getInstance(locale);
+ }
+
+ @Override
+ public HttpTransport getHttpTransport() {
+ return new NetHttpTransport();
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java
similarity index 52%
rename from MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
rename to MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java
index f432fe8fa..932601e76 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiPlatformAdapter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiLevelAdapter.java
@@ -15,21 +15,31 @@
*/
package com.google.android.apps.mytracks.util;
+import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
+import com.google.android.apps.mytracks.services.tasks.PeriodicTask;
+import com.google.api.client.http.HttpTransport;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
+import android.content.Context;
import android.content.SharedPreferences;
+import java.text.DecimalFormatSymbols;
+import java.util.Locale;
+
/**
- * A set of methods that may be implemented in a platform specific way.
+ * A set of methods that may be implemented differently depending on the Android API level.
*
* @author Bartlomiej Niechwiej
*/
-public interface ApiPlatformAdapter {
+public interface ApiLevelAdapter {
/**
* Puts the specified service into foreground.
*
+ * Due to changes in API level 5.
+ *
* @param service the service to be put in foreground.
* @param notificationManager the notification manager used to post the given
* notification.
@@ -41,6 +51,9 @@ public interface ApiPlatformAdapter {
/**
* Puts the given service into background.
+ *
+ * Due to changes in API level 5.
+ *
* @param service the service to put into background.
* @param notificationManager the notification manager to user when removing
* notifications.
@@ -50,14 +63,64 @@ public interface ApiPlatformAdapter {
void stopForeground(Service service, NotificationManager notificationManager,
int id);
+ /**
+ * Gets a status announcer task.
+ *
+ * Due to changes in API level 8.
+ */
+ PeriodicTask getStatusAnnouncerTask(Context context);
+
+ /**
+ * Gets a {@link BackupPreferencesListener}.
+ *
+ * Due to changes in API level 8.
+ */
+ BackupPreferencesListener getBackupPreferencesListener(Context context);
+
/**
* Applies all changes done to the given preferences editor.
* Changes may or may not be applied immediately.
+ *
+ * Due to changes in API level 9.
*/
void applyPreferenceChanges(SharedPreferences.Editor editor);
/**
* Enables strict mode where supported, only if this is a development build.
+ *
+ * Due to changes in API level 9.
*/
void enableStrictMode();
+
+ /**
+ * Copies elements from the input byte array into a new byte array, from
+ * indexes start (inclusive) to end (exclusive). The end index must be less
+ * than or equal to input.length.
+ *
+ * Due to changes in API level 9.
+ *
+ * @param input the input byte array
+ * @param start the start index
+ * @param end the end index
+ * @return a new array containing elements from the input byte array
+ */
+ byte[] copyByteArray(byte[] input, int start, int end);
+
+
+ /**
+ * Gets an instance of {@link DecimalFormatSymbols} for a locale.
+ *
+ * Due to changes in API level 9.
+ *
+ * @param locale the locale
+ * @return a new instance of {@link DecimalFormatSymbols}
+ */
+ DecimalFormatSymbols getDecimalFormatSymbols(Locale locale);
+
+ /**
+ * Gets a {@link HttpTransport}.
+ *
+ * Due to changes in API level 9.
+ */
+ HttpTransport getHttpTransport();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java
index c524ca917..609a7a9cf 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/UnitConversions.java
@@ -28,7 +28,8 @@ public abstract class UnitConversions {
public static final double MI_TO_FEET = 5280.0;
public static final double KMH_TO_MPH = 1000 * M_TO_FT / MI_TO_FEET;
public static final double TO_RADIANS = Math.PI / 180.0;
-
+ public static final double MPH_TO_KMH = 1.609344;
+
protected UnitConversions() {
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
index 5ee51fdc0..e62a05a26 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/widgets/TrackWidgetProvider.java
@@ -19,6 +19,16 @@ package com.google.android.apps.mytracks.widgets;
import static com.google.android.apps.mytracks.Constants.SETTINGS_NAME;
import static com.google.android.apps.mytracks.Constants.TAG;
+import com.google.android.apps.mytracks.MyTracks;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.Track;
+import com.google.android.apps.mytracks.content.TracksColumns;
+import com.google.android.apps.mytracks.services.ControlRecordingService;
+import com.google.android.apps.mytracks.stats.TripStatistics;
+import com.google.android.apps.mytracks.util.StringUtils;
+import com.google.android.apps.mytracks.util.UnitConversions;
+import com.google.android.maps.mytracks.R;
+
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -32,16 +42,6 @@ import android.os.Handler;
import android.util.Log;
import android.widget.RemoteViews;
-import com.google.android.apps.mytracks.MyTracks;
-import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
-import com.google.android.apps.mytracks.content.Track;
-import com.google.android.apps.mytracks.content.TracksColumns;
-import com.google.android.apps.mytracks.services.TrackRecordingService;
-import com.google.android.apps.mytracks.stats.TripStatistics;
-import com.google.android.apps.mytracks.util.StringUtils;
-import com.google.android.apps.mytracks.util.UnitConversions;
-import com.google.android.maps.mytracks.R;
-
/**
* An AppWidgetProvider for displaying key track statistics (distance, time,
* speed) from the current or most recent track.
@@ -175,14 +175,12 @@ public class TrackWidgetProvider
// If a new track is started by this appwidget or elsewhere,
// toggle the button to active and have it disable the track if pressed.
setButtonIntent(
- views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled,
- -1);
+ views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled);
} else {
// If a track is stopped by this appwidget or elsewhere,
// toggle the button to inactive and have it start a new track if pressed.
setButtonIntent(
- views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled,
- R.string.select_new_track_extra);
+ views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled);
}
}
@@ -193,15 +191,11 @@ public class TrackWidgetProvider
* @param context The widget context
* @param action The resource id of the action to fire when the button is pressed
* @param icon The resource id of the icon to show for the button
- * @param extra Optional resource id of a boolean extra on the intent
*/
private void setButtonIntent(
- RemoteViews views, Context context, int action, int icon, int extra) {
- Intent intent = new Intent(context, TrackRecordingService.class);
+ RemoteViews views, Context context, int action, int icon) {
+ Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(context.getString(action));
- if (extra != -1) {
- intent.putExtra(context.getString(extra), true);
- }
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
diff --git a/MyTracksApiSample/.classpath b/MyTracksApiSample/.classpath
new file mode 100644
index 000000000..d98e37d5d
--- /dev/null
+++ b/MyTracksApiSample/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/MyTracksApiSample/.project b/MyTracksApiSample/.project
new file mode 100644
index 000000000..f7a2037ed
--- /dev/null
+++ b/MyTracksApiSample/.project
@@ -0,0 +1,40 @@
+
+
+ MyTracksApiSample
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
+
+ MyTracksLib_src
+ 2
+ _android_MyTracksLib_e2802071/src
+
+
+
diff --git a/MyTracksApiSample/.settings/org.eclipse.jdt.core.prefs b/MyTracksApiSample/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..a1c77fa36
--- /dev/null
+++ b/MyTracksApiSample/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,282 @@
+#Thu Oct 13 15:26:00 PDT 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=569
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_member=569
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package_declaration=569
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=24
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type_declaration=569
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_field_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_for_statement=16
+org.eclipse.jdt.core.formatter.alignment_for_generic_type_arguments=16
+org.eclipse.jdt.core.formatter.alignment_for_local_variable_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_new_anonymous_class=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment_new_line_at_start_of_html_paragraph=true
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.force_if_else_statement_brace=false
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.sort_local_variable_annotations=false
+org.eclipse.jdt.core.formatter.sort_member_annotations=false
+org.eclipse.jdt.core.formatter.sort_package_annotations=false
+org.eclipse.jdt.core.formatter.sort_parameter_annotations=false
+org.eclipse.jdt.core.formatter.sort_type_annotations=false
+org.eclipse.jdt.core.formatter.tabulation.char=mixed
+org.eclipse.jdt.core.formatter.tabulation.size=8
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_comment_inline_tags=false
+org.eclipse.jdt.core.formatter.wrap_non_simple_local_variable_annotation=true
+org.eclipse.jdt.core.formatter.wrap_non_simple_member_annotation=true
+org.eclipse.jdt.core.formatter.wrap_non_simple_package_annotation=true
+org.eclipse.jdt.core.formatter.wrap_non_simple_parameter_annotation=false
+org.eclipse.jdt.core.formatter.wrap_non_simple_type_annotation=true
+org.eclipse.jdt.core.formatter.wrap_prefer_two_fragments=false
diff --git a/MyTracksApiSample/.settings/org.eclipse.jdt.ui.prefs b/MyTracksApiSample/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..57a8a95d5
--- /dev/null
+++ b/MyTracksApiSample/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,4 @@
+#Thu Oct 13 15:26:00 PDT 2011
+eclipse.preferences.version=1
+formatter_profile=org.eclipse.jdt.ui.default.sun_profile
+formatter_settings_version=11
diff --git a/MyTracksApiSample/AndroidManifest.xml b/MyTracksApiSample/AndroidManifest.xml
new file mode 100644
index 000000000..1c09cf1f9
--- /dev/null
+++ b/MyTracksApiSample/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MyTracksApiSample/default.properties b/MyTracksApiSample/default.properties
new file mode 100644
index 000000000..2a471ddd6
--- /dev/null
+++ b/MyTracksApiSample/default.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=Google Inc.:Google APIs:10
+android.library.reference.1=../MyTracksLib
diff --git a/MyTracksApiSample/proguard.cfg b/MyTracksApiSample/proguard.cfg
new file mode 100644
index 000000000..b1cdf17b5
--- /dev/null
+++ b/MyTracksApiSample/proguard.cfg
@@ -0,0 +1,40 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class * extends android.app.backup.BackupAgentHelper
+-keep public class * extends android.preference.Preference
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+ native ;
+}
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+ public (android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.app.Activity {
+ public void *(android.view.View);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+ public static final android.os.Parcelable$Creator *;
+}
diff --git a/MyTracksApiSample/res/drawable-hdpi/icon.png b/MyTracksApiSample/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..8074c4c57
Binary files /dev/null and b/MyTracksApiSample/res/drawable-hdpi/icon.png differ
diff --git a/MyTracksApiSample/res/drawable-ldpi/icon.png b/MyTracksApiSample/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..1095584ec
Binary files /dev/null and b/MyTracksApiSample/res/drawable-ldpi/icon.png differ
diff --git a/MyTracksApiSample/res/drawable-mdpi/icon.png b/MyTracksApiSample/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..a07c69fa5
Binary files /dev/null and b/MyTracksApiSample/res/drawable-mdpi/icon.png differ
diff --git a/MyTracksApiSample/res/layout/main.xml b/MyTracksApiSample/res/layout/main.xml
new file mode 100644
index 000000000..cad11badf
--- /dev/null
+++ b/MyTracksApiSample/res/layout/main.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/MyTracksApiSample/res/values/strings.xml b/MyTracksApiSample/res/values/strings.xml
new file mode 100644
index 000000000..cb332de6c
--- /dev/null
+++ b/MyTracksApiSample/res/values/strings.xml
@@ -0,0 +1,7 @@
+
+
+ Add a waypoint to all the tracks
+ MyTracksApiSample
+ Start recording
+ Stop recording
+
diff --git a/MyTracksApiSample/src/com/google/android/apps/mytracks/samples/api/MainActivity.java b/MyTracksApiSample/src/com/google/android/apps/mytracks/samples/api/MainActivity.java
new file mode 100644
index 000000000..319b0f848
--- /dev/null
+++ b/MyTracksApiSample/src/com/google/android/apps/mytracks/samples/api/MainActivity.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2011 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.samples.api;
+
+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.services.ITrackRecordingService;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.util.Calendar;
+import java.util.List;
+
+/**
+ * An activity to access MyTracks content provider and service.
+ *
+ * Note you must first install MyTracks before installing this app.
+ *
+ * You also need to enable third party application access inside MyTracks
+ * MyTracks -> menu -> Settings -> Sharing -> Allow access
+ *
+ * @author Jimmy Shih
+ */
+public class MainActivity extends Activity {
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+
+ // utils to access the MyTracks content provider
+ private MyTracksProviderUtils myTracksProviderUtils;
+
+ // display output from the MyTracks content provider
+ private TextView outputTextView;
+
+ // MyTracks service
+ private ITrackRecordingService myTracksService;
+
+ // intent to access the MyTracks service
+ private Intent intent;
+
+ // connection to the MyTracks service
+ private ServiceConnection serviceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ myTracksService = ITrackRecordingService.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ myTracksService = null;
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ // for the MyTracks content provider
+ myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
+ outputTextView = (TextView) findViewById(R.id.output);
+
+ Button addWaypointsButton = (Button) findViewById(R.id.add_waypoints_button);
+ addWaypointsButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ List