Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2eb36aedeb | |||
| 0e623ebfeb | |||
| ffb73569e9 | |||
| 713cda9455 | |||
| 223721cca8 | |||
| e7479c8a93 | |||
| 224fe0069b | |||
| cb97847392 | |||
| 6a29531467 | |||
| 262e249d2c | |||
| 77479efaef | |||
| 898863a8d9 | |||
| 05d555f98d | |||
| 868092db10 | |||
| 7a738c2eca | |||
| b304dbcba6 | |||
| 6e28a5daa4 | |||
| 8f4f9660ed | |||
| 2d01535651 | |||
| e57d4669fc | |||
| f47c599dea | |||
| 7a693a90f7 | |||
| 9cf51de3f7 | |||
| 5089c20cd6 | |||
| a0da23f86a | |||
| 44d24b57c6 | |||
| c143d774d4 | |||
| 9e92240dd0 | |||
| d2c586b096 | |||
| fbfbb9408a | |||
| fb56665dec | |||
| 5b65c216b6 | |||
| 9ad98c5970 | |||
| cae9d0f2e5 | |||
| c0d267c63e | |||
| 70704b0a5e | |||
| b135275201 | |||
| 7e1eae6681 | |||
| 30f5d6dc85 | |||
| a50dd1168b | |||
| 62fa1620aa | |||
| a0d42c8b59 | |||
| a21663da61 | |||
| 67a0382942 | |||
| f5c2467d01 | |||
| a3abcc8094 | |||
| edf439f301 | |||
| 2245563250 | |||
| bca21d5515 | |||
| 89cae2e7c9 | |||
| 26c381a4c4 | |||
| 4bc5aa050c | |||
| cd44b0d180 | |||
| 59ee91ca5d | |||
| 59165a7033 | |||
| 93d242c05f | |||
| 4bbb6591e3 | |||
| 7df8025f6a | |||
| f19c52f40e | |||
| b7840779fe | |||
| 28042a8cc9 | |||
| b2907226bf | |||
| a2f46821f5 | |||
| b7385cd4cc | |||
| 718c65818c | |||
| 91e2293606 | |||
| 3c4a921f9a | |||
| d04b333126 | |||
| 0a4bef4d9a | |||
| a0f090a83a | |||
| dc8ee5943c | |||
| 9132c36aa7 | |||
| 224c0d759f | |||
| ffcaca2db1 | |||
| 409fc4a43b | |||
| 16968f3f2b | |||
| e3fd0dce21 | |||
| 53b6b6deca | |||
| 26f47a50d0 | |||
| 3cf77374c4 | |||
| a1129c3f0f | |||
| 2fe74603a3 | |||
| 54d517363d | |||
| 948fe0c0e6 | |||
| dbd8e6c0e9 | |||
| 02b5601933 | |||
| 3ed40608a2 | |||
| 36d7c2dd91 | |||
| fa7ef68851 | |||
| f84877a4fc | |||
| 261fb62e24 | |||
| 48d8aa4cd8 | |||
| 95ff0d3db6 | |||
| 2dc1d914c0 | |||
| fc06701009 | |||
| ee4f334296 | |||
| cac2395237 | |||
| 5afc3f13cf | |||
| 9030917c14 | |||
| 9905def352 | |||
| a3793aa92b | |||
| 76c8498956 | |||
| 75bd8ea5a2 | |||
| 50a5e864ac | |||
| 352fc26f7e | |||
| 44afa1623e | |||
| 14acafabfc | |||
| 773af58d03 | |||
| 352b851e9c | |||
| 8353a01082 | |||
| b70e001206 | |||
| a741bb4d55 | |||
| 24adee3d54 | |||
| 09203ffb95 | |||
| e94338f4c6 | |||
| dd9dd8ee81 | |||
| 01d6dcac59 | |||
| ad0ee6b0d6 | |||
| 769c8c228e | |||
| 50fcd67085 | |||
| 52d03e6b7d | |||
| b949cd0039 | |||
| e527170315 | |||
| ec7d210c36 | |||
| 74a4089464 | |||
| fa9e14f574 | |||
| cdbeefcd16 | |||
| ffdd5f4091 | |||
| bb60525780 | |||
| 82634af649 | |||
| 3643ed9f13 | |||
| fac59ab705 | |||
| 1e343b0d12 | |||
| 34bb467b97 | |||
| b092525c10 | |||
| 256dac3a82 | |||
| dfe947f54d | |||
| ce09407816 | |||
| beee6af79d | |||
| c282562867 | |||
| 2aed9b3a33 | |||
| 210910f21e | |||
| 1d666318ae | |||
| 4df6095089 | |||
| d4edcce224 | |||
| d7c6761841 | |||
| 5733224f8d | |||
| 22fd5042bc | |||
| 44a6f2d448 | |||
| cd9c17f9b3 | |||
| dc07bcf0fa | |||
| 248b8fb4fd | |||
| 35182a1483 | |||
| 21b0a517a9 | |||
| 19a770445e | |||
| c6512f1376 | |||
| 2cb3963138 | |||
| d05c394c68 | |||
| 797de31960 | |||
| 872d2d4337 | |||
| b84da40365 | |||
| 236bcba2bd | |||
| 0347877290 | |||
| b464526d5e | |||
| 0ccd05e605 | |||
| 916e4586c5 |
@@ -3,3 +3,4 @@ syntax:glob
|
||||
*/bin
|
||||
*/gen/**
|
||||
.metadata
|
||||
MyTracksTest/annotations.log
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
||||
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
||||
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
||||
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
||||
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#Thu Aug 12 18:54:52 BRT 2010
|
||||
#Wed Sep 29 18:44:16 BRT 2010
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.maps.mytracks"
|
||||
android:versionCode="18"
|
||||
android:versionName="1.0.17">
|
||||
android:installLocation="auto"
|
||||
android:versionCode="21"
|
||||
android:versionName="1.0.20">
|
||||
<application android:icon="@drawable/arrow_icon"
|
||||
android:label="@string/app_name"
|
||||
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
|
||||
@@ -12,7 +13,6 @@
|
||||
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
|
||||
|
||||
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
|
||||
android:multiprocess="true"
|
||||
android:authorities="com.google.android.maps.mytracks" />
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
||||
@@ -56,8 +56,11 @@
|
||||
<activity android:name="com.google.android.apps.mytracks.MyTracksSettings"
|
||||
android:label="@string/mytracks_settings" />
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
|
||||
android:label="@string/aggregated_stats" />
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
|
||||
android:label="@string/statistics"/>
|
||||
android:label="@string/statistics" />
|
||||
|
||||
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
|
||||
android:label="@string/elevation_label"/>
|
||||
@@ -66,14 +69,21 @@
|
||||
android:label="@string/welcome"/>
|
||||
|
||||
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
||||
android:process=":trackrecord"
|
||||
android:label="Track Recording Service"
|
||||
android:icon="@drawable/arrow_320"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<receiver android:name="com.google.android.apps.mytracks.MyTracksReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
<!-- Permissions to initialize service -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<!-- Permissions for recording tracks and showing maps -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
@@ -90,6 +100,22 @@
|
||||
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.writely" />
|
||||
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
||||
|
||||
<!-- Permissions for misc services -->
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
|
||||
<!-- Features required -->
|
||||
<uses-feature android:name="android.hardware.location" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.location.gps" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="true"/>
|
||||
|
||||
<!-- Features optional -->
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.sensor.compass" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.telephony.gsm" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false"/>
|
||||
|
||||
<uses-sdk android:minSdkVersion="3"/>
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
@@ -97,4 +123,3 @@
|
||||
android:smallScreens="true"
|
||||
android:anyDensity="true" />
|
||||
</manifest>
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/MyTracks"/>
|
||||
<listEntry value="/MyTracks/AndroidManifest.xml"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
<listEntry value="1"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -17,11 +17,11 @@ limitations under the License.
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/btn_arrow_left_default" />
|
||||
android:drawable="@drawable/left_arrow_chart" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/btn_arrow_left_press" />
|
||||
android:drawable="@drawable/left_arrow_chart_press" />
|
||||
|
||||
<item android:drawable="@drawable/btn_arrow_left_default" />
|
||||
<item android:drawable="@drawable/left_arrow_chart" />
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -17,11 +17,11 @@ limitations under the License.
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/btn_arrow_right_default" />
|
||||
android:drawable="@drawable/left_arrow_stats" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/btn_arrow_right_press" />
|
||||
android:drawable="@drawable/left_arrow_stats_press" />
|
||||
|
||||
<item android:drawable="@drawable/btn_arrow_right_default" />
|
||||
<item android:drawable="@drawable/left_arrow_stats" />
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/left_arrow_track" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/left_arrow_track_press" />
|
||||
|
||||
<item android:drawable="@drawable/left_arrow_track" />
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_chart" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_chart_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_chart" />
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_stats" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_stats_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_stats" />
|
||||
|
||||
</selector>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_window_focused="false"
|
||||
android:drawable="@drawable/right_arrow_track" />
|
||||
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/right_arrow_track_press" />
|
||||
|
||||
<item android:drawable="@drawable/right_arrow_track" />
|
||||
|
||||
</selector>
|
||||
@@ -20,10 +20,15 @@
|
||||
android:icon="@android:drawable/ic_menu_preferences"
|
||||
android:orderInCategory="1001"
|
||||
android:title="@string/settings"/>
|
||||
|
||||
<item android:id="@+id/menu_aggregated_stats"
|
||||
android:icon="@android:drawable/ic_menu_info_details"
|
||||
android:orderInCategory="1002"
|
||||
android:title="@string/aggregated_stats"/>
|
||||
|
||||
<item android:id="@+id/menu_help"
|
||||
android:icon="@android:drawable/ic_menu_info_details"
|
||||
android:orderInCategory="1002"
|
||||
android:orderInCategory="1003"
|
||||
android:title="@string/help"/>
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -155,7 +155,6 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Dauer zwischen Ansagen:</string>
|
||||
<string name="settings_announcement_frequency_summary">Kürzeste Zeit zwichen zwei Ansagen</string>
|
||||
<string name="settings_announcement_not_available_summary">Gesprochene Ansagen sind mit Ihrer Version von Android nicht verfügbar.</string>
|
||||
|
||||
<string name="settings_min_recording_distance">Kleinste Entfernung zwischen Punkten:</string>
|
||||
<string name="settings_min_recording_distance_summary">Kleinste Entfernung zwischen zwei Positionsaufzeichnungen</string>
|
||||
|
||||
@@ -158,7 +158,6 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Tiempo entre anuncios</string>
|
||||
<string name="settings_announcement_frequency_summary">Tiempo mínimo entre dos anuncios de voz</string>
|
||||
<string name="settings_announcement_not_available_summary">Conversión de anuncios de texto a voz no esta disponible en su versión de Android</string>
|
||||
|
||||
<string name="settings_min_recording_distance">Distancia mínima entre puntos consecutivos</string>
|
||||
<string name="settings_min_recording_distance_summary">Distancia mínima entre dos grabaciones de posición</string>
|
||||
|
||||
@@ -158,7 +158,6 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Fréquence des annonces</string>
|
||||
<string name="settings_announcement_frequency_summary">Temps minimum entre eux annonces vocales</string>
|
||||
<string name="settings_announcement_not_available_summary">La synthèse vocale n\'est pas disponible pour cette version d\'Android</string>
|
||||
|
||||
<string name="settings_min_recording_distance">Distance min. entre 2 points</string>
|
||||
<string name="settings_min_recording_distance_summary">Plus petite distance entre deux enregistrements de votre position</string>
|
||||
|
||||
@@ -164,7 +164,6 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Intervallo fra due annunci:</string>
|
||||
<string name="settings_announcement_frequency_summary">Tempo minimo fra due annunci audio</string>
|
||||
<string name="settings_announcement_not_available_summary">Gli annunci audio non sono disponibili in questa varsione di Android</string>
|
||||
|
||||
<string name="settings_min_recording_distance">Distanza minima fra due punti successivi:</string>
|
||||
<string name="settings_min_recording_distance_summary">Distanza minima fra due successive registrazioni della posizione</string>
|
||||
|
||||
@@ -164,7 +164,7 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">定期通知</string>
|
||||
<string name="settings_announcement_frequency_summary">音声通知間隔の最小時間</string>
|
||||
<string name="settings_announcement_not_available_summary">お使いのAndroidの版では音声読み上げ機能がありません</string>
|
||||
|
||||
|
||||
<string name="settings_min_recording_distance">ポイント間の最短距離</string>
|
||||
<string name="settings_min_recording_distance_summary">位置記録間の最短距離</string>
|
||||
|
||||
@@ -146,6 +146,15 @@ limitations under the License.
|
||||
<item>2 min</item>
|
||||
<item>1 min</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Auto-resume track timeout -->
|
||||
<string-array name="auto_resume_track_timeout_options">
|
||||
<item>Nigdy</item>
|
||||
<item>5 min</item>
|
||||
<item>10 min</item>
|
||||
<item>15 min</item>
|
||||
<item>Zawsze</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Other non-preference arrays -->
|
||||
<array name="activity_types">
|
||||
|
||||
@@ -29,6 +29,7 @@ limitations under the License.
|
||||
<string name="choose_track_message">Wybierz trasę do podglądu</string>
|
||||
<string name="current_track">Obecna trasa</string>
|
||||
<string name="current_segment">Ostatni segment</string>
|
||||
<string name="aggregated_stats">Statystyki Grupowe</string>
|
||||
|
||||
<!-- Error messages -->
|
||||
<string name="error_no_location_manager">Brak menedżera lokalizacji na urządzeniu!</string>
|
||||
@@ -75,9 +76,9 @@ limitations under the License.
|
||||
|
||||
<!-- Used by DeleteAllDataHandler -->
|
||||
<string name="are_you_sure_question">Jesteś pewien?</string>
|
||||
<string name="all_data_will_be_permanently_deleted">Wszystkie trasy i znaczniki zostaną usunięte z tego telefonu.</string>
|
||||
<string name="track_will_be_permanently_deleted">Trasy i ich znaczniki zostaną całkowicie usunięte z tego telefonu.</string>
|
||||
<string name="marker_will_be_permanently_deleted">Znacznik zostanie całkowicie usunięty z tego telefonu.</string>
|
||||
<string name="all_data_will_be_permanently_deleted">Wszystkie trasy i znaczniki zostaną usunięte z telefonu.</string>
|
||||
<string name="track_will_be_permanently_deleted">Trasa i jej znaczniki zostanie całkowicie usunięta z telefonu.</string>
|
||||
<string name="marker_will_be_permanently_deleted">Znacznik zostanie całkowicie usunięty z telefonu.</string>
|
||||
|
||||
<!-- Text and links when uploading to Google My Maps -->
|
||||
<string name="new_map_description">Utworzono za pomocą My Tracks na Android.</string>
|
||||
@@ -164,7 +165,7 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Częstotliwość komunikatów</string>
|
||||
<string name="settings_announcement_frequency_summary">Minimalny czas między dwoma komunikatami głosowymi</string>
|
||||
<string name="settings_announcement_not_available_summary">Brak wsparcia dla syntezy mowy w twojej wersji Androida</string>
|
||||
|
||||
|
||||
<string name="settings_min_recording_distance">Min. odległość między punktami</string>
|
||||
<string name="settings_min_recording_distance_summary">Najmniejsza odległość między dwoma zapisami twojej pozycji</string>
|
||||
@@ -192,14 +193,45 @@ limitations under the License.
|
||||
|
||||
<string name="settings_share_url_only">Dziel się tylko odnośnikiem</string>
|
||||
<string name="settings_share_url_only_summary_on">Tylko odnośnik do mapy będzie wysyłany podczas dzielenia się</string>
|
||||
<string name="settings_share_url_only_summary_off">Zawartość trasy również będzie przesyłana</string>
|
||||
<string name="settings_share_url_only_summary_off">Zawartość trasy będzie również przesyłana</string>
|
||||
|
||||
<string name="settings_signal_sampling_frequency">Częstotliowść próbkowania sygnału</string>
|
||||
<string name="settings_signal_sampling_frequency_summary">Czas między dwoma zapisami siły sygnału telefonu</string>
|
||||
|
||||
<string name="settings_split_frequency">Częstotliwość rozdzielania</string>
|
||||
<string name="settings_split_frequency_summary">Maksymalny czas lub odległość po którym wstawiony zostanie znacznik rozdzielenia</string>
|
||||
|
||||
<string name="settings_auto_resume_track_timeout">Automatyczne wznawianie rejestracji trasy</string>
|
||||
<string name="settings_auto_resume_track_timeout_summary">Maksymalny czas w minutach, który upłynął od ostatniego restartu telefonu, a po którym nie zostania wznowiona poprzednia trasa</string>
|
||||
|
||||
<string name="pace_label">Tempo</string>
|
||||
|
||||
<string name="eclair_required">Dostępne tylko w wersji Android 2.0 lub nowszej</string>
|
||||
<string name="settings_sensor_type">Typ Czujnika</string>
|
||||
<string name="settings_sensor_type_summary">Typ czujnika do podłączenia.</string>
|
||||
<string name="settings_sensor_id">Id Czujnika</string>
|
||||
<string name="settings_sensor_id_summary">Identyfikator wysyłany do czujników.</string>
|
||||
<string name="sensor_id_malformed">Identyfikator musi być liczbą całkowitą.</string>
|
||||
<string name="bluetooth_sensor_title">Czujnik bluetooth</string>
|
||||
<string name="bluetooth_sensor_summary">Czujnik bluetooth do podłączenia.</string>
|
||||
<string name="settings_modular_stats_display_title">Widok Statystyk Modularnych</string>
|
||||
<string name="settings_modular_stats_display_summary_on">Ekran statystyk jest podzielony w zależności od trybu.</string>
|
||||
<string name="settings_modular_stats_display_summary_off">Wyświetl wszystkie statystyki na jednym ekranie.</string>
|
||||
|
||||
<string name="settings_backup">Kopia zapasowa (BETA)</string>
|
||||
<string name="settings_sd_backup">Kopia zapasowa na kartę SD</string>
|
||||
<string name="settings_backup_to_sd">Utwórz kopię</string>
|
||||
<string name="settings_backup_to_sd_summary">Tworzy kopię zapasową wszystkich tras i ustawień i zapisuje je na karcie SD</string>
|
||||
<string name="settings_restore_from_sd">Odtwórz z kopii</string>
|
||||
<string name="settings_restore_from_sd_summary">Odtwarza wszystkie trasy i ustawienia z kopii zapasowej z karty SD</string>
|
||||
<string name="settings_no_backup_while_recording">Nie można tworzyć/odtwarzać kopii zapasowej podczas rejestrowania trasy.</string>
|
||||
|
||||
<!-- Backups -->
|
||||
<string name="restore_overwrites_warning">Odtworzenie z kopii zapasowej z karty SD zastąpi wszystkie bieżące trasy i ustawienia. Jesteś tego pewien?</string>
|
||||
<string name="select_backup_to_restore">Wybierz kopię do odtworzenia</string>
|
||||
<string name="no_backups">Nie znaleziono kopii zapasowych.</string>
|
||||
<string name="backup_write_progress_message">Zapisywanie wszystkich tras i ustawień na kartę SD. To może trochę potrwać...</string>
|
||||
<string name="backup_import_progress_message">Odczytywanie wszystkich tras i ustawień z karty SD. To może trochę potrwać...</string>
|
||||
|
||||
<!-- MyTracksSky -->
|
||||
<string name="mytracks_sky">Satelity GPS</string>
|
||||
@@ -351,8 +383,10 @@ limitations under the License.
|
||||
<!-- SD card -->
|
||||
<string name="io_no_external_storage_found">Nie znaleziono zewnętrznej pamięci. Upewniej się, że karta SD jest w telefonie i nie jest podłączona do komputera.</string>
|
||||
<string name="io_write_failed">Nie można zapisać pliku.</string>
|
||||
<string name="io_read_failed">Nie można odczytać pliku.</string>
|
||||
<string name="io_create_dir_failed">Nie można utworzyć katalogu na karcie SD.</string>
|
||||
<string name="io_write_finished">Zakończono zapisywanie trasy na karcie SD.</string>
|
||||
<string name="io_write_finished">Zakończono zapisywanie na karcie SD.</string>
|
||||
<string name="io_read_finished">Zakończono odczytywanie z karty SD.</string>
|
||||
|
||||
<!-- VersionChecker -->
|
||||
<string name="version_check_title">Sprawdzanie aktualizacji</string>
|
||||
|
||||
@@ -157,7 +157,6 @@ limitations under the License.
|
||||
<string name="settings_sharing">Compartilhamento</string>
|
||||
<string name="settings_announcement_frequency">Tempo entre anúncios</string>
|
||||
<string name="settings_announcement_frequency_summary">Tempo mínimo entre dois anúncios de voz</string>
|
||||
<string name="settings_announcement_not_available_summary">Anúncios de voz não estão disponíveis em sua versão do Android</string>
|
||||
<string name="settings_min_recording_distance">Dist. mín. entre pontos</string>
|
||||
<string name="settings_min_recording_distance_summary">Menor distância entre duas gravações da sua posição</string>
|
||||
<string name="settings_max_recording_distance">Dist. máx. entre pontos</string>
|
||||
|
||||
@@ -34,8 +34,8 @@ limitations under the License.
|
||||
<!-- Error messages -->
|
||||
<string name="error_no_location_manager">Ingen platshanterare hittades på denna enhet!</string>
|
||||
<string name="error_no_gps_location_provider">Ingen GPS-positioneringstjänst hittades på denna enhet.</string>
|
||||
<string name="error_sending_to_mymap">Det uppstod ett fel i sändningen av ditt spår till Google My Map. Försök igen senare.</string>
|
||||
<string name="error_sending_to_docs">Det uppstod ett fel i sändningen av ditt spår till Google Docs. Försök igen senare.</string>
|
||||
<string name="error_sending_to_mymap">Det uppstod ett fel i sändningen av ditt spår till Google Mina Kartor. Försök igen senare.</string>
|
||||
<string name="error_sending_to_docs">Det uppstod ett fel i sändningen av ditt spår till Google Dokument. Försök igen senare.</string>
|
||||
<string name="error_unable_to_start_recording">Kan inte starta inspelningen. Försök igen.</string>
|
||||
<string name="error_unable_to_stop_recording">Kan inte stoppa inspelningen. Försök igen.</string>
|
||||
<string name="error_unable_to_read_file">Kan inte läsa från fil.</string>
|
||||
@@ -43,19 +43,19 @@ limitations under the License.
|
||||
<string name="error_generic">Ett fel uppstod. Försök igen.</string>
|
||||
<string name="error_track_does_not_exist">Spåret finns inte.</string>
|
||||
<string name="error_out_of_memory">Den här filen är för stor (måste vara mindre än 500 KB). Avbryter.</string>
|
||||
<string name="error_unable_to_insert_marker">Kan inte skapa markör. Ingen possition tillgänglig.</string>
|
||||
<string name="error_unable_to_insert_marker">Kan inte skapa markör. Ingen position tillgänglig.</string>
|
||||
|
||||
<!-- Status messages -->
|
||||
<string name="wait_for_fix">Väntar på bättre GPS-posstion. Se till att himlen är synlig.</string>
|
||||
<string name="wait_for_fix">Väntar på bättre GPS-position. Se till att himlen är synlig.</string>
|
||||
<string name="status_now_recording">Spelar nu in ett nytt spår.</string>
|
||||
<string name="status_stopped_recording">Stoppade inspelning av ditt spår.</string>
|
||||
<string name="status_new_mymap_has_been_created">En ny Google My Map har skapats på maps.google.com.</string>
|
||||
<string name="status_new_mymap_has_been_created">En ny Google Mina Kartor har skapats på maps.google.com.</string>
|
||||
<string name="status_tracks_have_been_uploaded">Ditt spår har skickats till maps.google.com.</string>
|
||||
<string name="status_tracks_have_been_uploaded_to_new_doc">Ditt spår har skockats till ett nytt kalkylblad på docs.google.com.</string>
|
||||
<string name="status_tracks_have_been_uploaded_to_new_doc">Ditt spår har skickats till ett nytt kalkylblad på docs.google.com.</string>
|
||||
<string name="status_tracks_have_been_uploaded_to_docs">Ditt spår har skickats till docs.google.com.</string>
|
||||
<string name="status_mymap_info">Det kan ibland ta några minuter innan nya spår visas.</string>
|
||||
<string name="status_enable_gps">Klicka här för att gå till Säkerhet och Platsinställningar och kryssa i \'aktivera gps-satteliter\'. Tryck på tillbakaknappen för att återgå.</string>
|
||||
<string name="status_approximate_location">OBS: En ungerfärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
|
||||
<string name="status_approximate_location">OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
|
||||
<string name="status_no_location">En position kan för närvarande inte fastställas. Aktivera mer platskällor (se Säkerhet och Platsinställningar) och se till att himlen är synlig. Försök igen senare.</string>
|
||||
<string name="status_waypoint_inserted">En positionsmarkör har lagts in.</string>
|
||||
<string name="status_statistics_inserted">En statistikmarkör har lagts in.</string>
|
||||
@@ -65,13 +65,13 @@ limitations under the License.
|
||||
<string name="progress_message">Skickar ditt spår till Google.</string>
|
||||
<string name="progress_message_reading_track">Läser spår...</string>
|
||||
<string name="progress_message_preparing_track">Förbereder spår...</string>
|
||||
<string name="progress_message_authenticating_mymaps">Autentiserar till Google Maps...</string>
|
||||
<string name="progress_message_authenticating_docs">Autentiserar till Google Docs...</string>
|
||||
<string name="progress_message_sending_docs">Skickar spår till Google Docs...</string>
|
||||
<string name="progress_message_sending_mymaps">Skickar spår till Google Maps...</string>
|
||||
<string name="progress_message_creating_map">Skapar en ny My Map...</string>
|
||||
<string name="progress_message_authenticating_mymaps">Autentiserar till Google Kartor...</string>
|
||||
<string name="progress_message_authenticating_docs">Autentiserar till Google Dokument...</string>
|
||||
<string name="progress_message_sending_docs">Skickar spår till Google Dokument...</string>
|
||||
<string name="progress_message_sending_mymaps">Skickar spår till Google Kartor...</string>
|
||||
<string name="progress_message_creating_map">Skapar en ny Min Karta...</string>
|
||||
<string name="import_progress_message">Importerar ett GPS-spår. Detta kan ta en stund.</string>
|
||||
<string name="write_progress_message">Skriver til till SD-kortet. Detta kan ta en stund.</string>
|
||||
<string name="write_progress_message">Skriver till SD-kortet. Detta kan ta en stund.</string>
|
||||
<string name="progress_message_updating_track">Uppdaterar spårstatistik...</string>
|
||||
|
||||
<!-- Used by DeleteAllDataHandler -->
|
||||
@@ -110,9 +110,9 @@ limitations under the License.
|
||||
|
||||
<!-- TrackList -->
|
||||
<string name="tracklist_show_track">Visa</string>
|
||||
<string name="tracklist_send_to_mymaps">Skicka till Google My Maps</string>
|
||||
<string name="tracklist_send_to_new_mymap">Skicka till New Google My Map</string>
|
||||
<string name="tracklist_send_to_docs">Skicka till Google Docs</string>
|
||||
<string name="tracklist_send_to_mymaps">Skicka till Google Mina Kartor</string>
|
||||
<string name="tracklist_send_to_new_mymap">Skicka till ny Google Mina Kartor</string>
|
||||
<string name="tracklist_send_to_docs">Skicka till Google Dokument</string>
|
||||
<string name="tracklist_send_to_google">Skicka till Google...</string>
|
||||
<string name="tracklist_delete_track">Ta bort från telefonen...</string>
|
||||
<string name="tracklist_edit_track">Ändra detaljer</string>
|
||||
@@ -120,16 +120,16 @@ limitations under the License.
|
||||
<string name="tracklist_save_as_gpx">Spara en GPX-fil</string>
|
||||
<string name="tracklist_save_as_kml">Spara en KML-fil</string>
|
||||
<string name="tracklist_save_as_csv">Spara en CSV-fil</string>
|
||||
<string name="tracklist_btn_send_all_to_mymaps">Skicka till Google My Maps</string>
|
||||
<string name="tracklist_btn_send_all_to_mymaps">Skicka till Google Mina Kartor</string>
|
||||
<string name="tracklist_btn_delete_all">Ta bort alla spår</string>
|
||||
<!-- Export All -->
|
||||
<string name="tracklist_btn_export_all">Exportera alla spår</string>
|
||||
<string name="export_done">Exporteringen slutfördes</string>
|
||||
<string name="tracklist_pick_one">Välj ett spår. TryckPress&håll för fler alternativ.</string>
|
||||
<string name="tracklist_pick_one">Välj ett spår. Tryck & håll för fler alternativ.</string>
|
||||
<string name="tracklist_all_tracks">Alla spår:</string>
|
||||
<string name="tracklist_this_track">Det här spåret</string>
|
||||
<string name="tracklist_share_track">Dela med vänner...</string>
|
||||
<string name="tracklist_share_link">En länk till Google My Map</string>
|
||||
<string name="tracklist_share_link">En länk till Google Mina Kartor</string>
|
||||
<string name="tracklist_share_gpx_file">En GPX-fil</string>
|
||||
<string name="tracklist_share_kml_file">En KML-fil</string>
|
||||
<string name="tracklist_share_csv_file">En CSV-fil</string>
|
||||
@@ -143,13 +143,13 @@ limitations under the License.
|
||||
<string name="waypointslist_show_waypoint">Visa på karta</string>
|
||||
<string name="waypointslist_btn_insert_waypoint">Infoga positionspunkt</string>
|
||||
<string name="waypointslist_btn_insert_statistics">Infoga statistik</string>
|
||||
<string name="waypointslist_pick_one">Välj en markör. TryckPress&håll för fler alternativ.</string>
|
||||
<string name="waypointslist_pick_one">Välj en markör. Tryck & håll för fler alternativ.</string>
|
||||
|
||||
<!-- MyTracks Details -->
|
||||
<string name="trackdetails_title">Spårdetaljer</string>
|
||||
<string name="trackdetails_name">Spårnamn</string>
|
||||
<string name="trackdetails_description">Spårbeskrivning</string>
|
||||
<string name="trackdetails_category">Aktivitetstyp (t ex vandring, cyckling, låpning, klättring...)</string>
|
||||
<string name="trackdetails_category">Aktivitetstyp (t ex vandring, cyckling, löpning, klättring...)</string>
|
||||
|
||||
<!-- MyTracks Waypoint Details -->
|
||||
<string name="waypointdetails_title">Markördetaljer</string>
|
||||
@@ -165,7 +165,7 @@ limitations under the License.
|
||||
|
||||
<string name="settings_announcement_frequency">Meddelandefrekvens</string>
|
||||
<string name="settings_announcement_frequency_summary">Minimumtid mellan två röstmeddelanden</string>
|
||||
<string name="settings_announcement_not_available_summary">Text-till-talmeddelanden är inte tillgängligt i din version av Android</string>
|
||||
|
||||
|
||||
<string name="settings_min_recording_distance">Min distans mellan två punkter</string>
|
||||
<string name="settings_min_recording_distance_summary">Minsta distans mellan två inspelningar av din position</string>
|
||||
@@ -185,7 +185,7 @@ limitations under the License.
|
||||
|
||||
<string name="settings_map_public">Gör kartor offentliga</string>
|
||||
<string name="settings_map_public_summary_on">Uppladdade kartor kommer att vara synliga för alla på maps.google.com</string>
|
||||
<string name="settings_map_public_summary_off">Uppladdad kartor kommer inte att visas offentligt på maps.google.com</string>
|
||||
<string name="settings_map_public_summary_off">Uppladdade kartor kommer inte att visas offentligt på maps.google.com</string>
|
||||
|
||||
<string name="settings_report_speed_or_pace">Visa hastighet</string>
|
||||
<string name="settings_report_speed_or_pace_summary_on">Hastighet kommer att visas</string>
|
||||
@@ -199,18 +199,18 @@ limitations under the License.
|
||||
<string name="settings_signal_sampling_frequency_summary">Tid mellan två inspelningar av telefonens signalstyrka</string>
|
||||
|
||||
<string name="settings_split_frequency">Delningsfrekvens</string>
|
||||
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delnginsmarkör kommer infogas</string>
|
||||
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delningsmarkör kommer infogas</string>
|
||||
<string name="pace_label">Tempo</string>
|
||||
|
||||
<!-- MyTracksSky -->
|
||||
<string name="mytracks_sky">GPS-satteliter</string>
|
||||
<string name="mytracks_sky">GPS-satelliter</string>
|
||||
|
||||
<!-- My Maps -->
|
||||
<string name="select_map_message">Välje en karta från ditt konto:</string>
|
||||
<string name="select_map_message">Välj en karta från ditt konto:</string>
|
||||
<string name="create_new_map">Skapa en ny karta...</string>
|
||||
<string name="map_list_title">Skicka till Google My Maps</string>
|
||||
<string name="map_list_title">Skicka till Google Mina Kartor</string>
|
||||
<string name="maps_list_loading">Laddar kartor...</string>
|
||||
<string name="maps_list_failed">Din kartor kunde inte laddas just nu!</string>
|
||||
<string name="maps_list_failed">Dina kartor kunde inte laddas just nu!</string>
|
||||
<string name="open_map">Välj den här kartan</string>
|
||||
<string name="edit_map_details">Ändra kartdetaljer</string>
|
||||
<string name="share_map">Dela kartan med vänner</string>
|
||||
@@ -219,7 +219,7 @@ limitations under the License.
|
||||
<string name="map_title">Karttitel:</string>
|
||||
<string name="map_description">Kartbeskrivning:</string>
|
||||
<string name="map_searchable_edit">Gör så att kartan och dess innehåll visas i sökresultat</string>
|
||||
<string name="map_not_searchable_edit">Låt inte kartan och dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänken</string>
|
||||
<string name="map_not_searchable_edit">Låt inte kartan och dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk</string>
|
||||
<string name="map_searchable">Kartan och dess innehåll kommer att visas i sökresultat</string>
|
||||
<string name="map_not_searchable">Varken kartan eller dess innehåll visas i sökresultat, men de kan ändå ses av andra genom en direktlänk</string>
|
||||
<string name="close">Stäng</string>
|
||||
@@ -240,7 +240,7 @@ limitations under the License.
|
||||
<!-- Visibility -->
|
||||
<string name="public_map">Publik</string>
|
||||
<string name="unlisted_map">Olistat</string>
|
||||
<string name="mapslist_no_maps_available">Du har inge kartor än.</string>
|
||||
<string name="mapslist_no_maps_available">Du har inga kartor än.</string>
|
||||
|
||||
<!-- Statistics View -->
|
||||
<!-- Average Moving -->
|
||||
@@ -253,14 +253,14 @@ limitations under the License.
|
||||
|
||||
<string name="elevation_chart_name">Höjddiagram</string>
|
||||
<string name="elevation_gain_label">Höjdvinst</string>
|
||||
<string name="elevation_label">Höjt</string>
|
||||
<string name="elevation_label">Höjd</string>
|
||||
<string name="latitude_label">Latitud</string>
|
||||
<string name="longitude_label">Longitud</string>
|
||||
|
||||
<!-- Min -->
|
||||
<string name="min_elevation_label">Min höjd</string>
|
||||
<string name="min_grade_label">Min lutning</string>
|
||||
<string name="min_pace_label">Min tempo</string>
|
||||
<string name="min_elevation_label">Minimihöjd</string>
|
||||
<string name="min_grade_label">Minimilutning</string>
|
||||
<string name="min_pace_label">Minimitempo</string>
|
||||
|
||||
<!-- Max -->
|
||||
<string name="max_elevation_label">Maxhöjd</string>
|
||||
@@ -272,12 +272,12 @@ limitations under the License.
|
||||
<!-- Chart Settings -->
|
||||
<string name="chart_settings">Diagraminställningar</string>
|
||||
<!-- X Axis -->
|
||||
<string name="x_axis">X Axel</string>
|
||||
<string name="x_axis">X-axel</string>
|
||||
<string name="by_distance">Efter distans</string>
|
||||
<string name="by_time">Efter tid</string>
|
||||
|
||||
<!-- Y Axis -->
|
||||
<string name="y_axis">Y Axel</string>
|
||||
<string name="y_axis">Y-axel</string>
|
||||
|
||||
<!-- Units -->
|
||||
<!-- Time -->
|
||||
@@ -300,16 +300,16 @@ limitations under the License.
|
||||
|
||||
<!-- Speed and Pace -->
|
||||
<string name="kilometer_per_hour">km/h</string>
|
||||
<string name="kilometer_per_hour_long">kilometer per timme</string>
|
||||
<string name="kilometer_per_hour_long">kilometer i timmen</string>
|
||||
<string name="min_per_kilometer">min/km</string>
|
||||
<string name="per_kilometer">per kilometer</string>
|
||||
<string name="mile_per_hour">mi/h</string>
|
||||
<string name="mile_per_hour_long">miles per timme</string>
|
||||
<string name="mile_per_hour_long">miles i timmen</string>
|
||||
<string name="min_per_mile">min/mi</string>
|
||||
<string name="per_mile">per mile</string>
|
||||
|
||||
<!-- Stats Labels -->
|
||||
<string name="moving_time_label">Rörelsetid</string>
|
||||
<string name="moving_time_label">Rörlig tid</string>
|
||||
<string name="total_time_label">Total tid</string>
|
||||
<string name="time_label">Tid</string>
|
||||
<string name="sd_unavail">SD otillgängligt</string>
|
||||
@@ -336,8 +336,8 @@ limitations under the License.
|
||||
|
||||
<!-- Send to Google -->
|
||||
<string name="sendtogoogle_title">Skicka till...</string>
|
||||
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
|
||||
<string name="sendtogoogle_google_docs">Google Docs</string>
|
||||
<string name="sendtogoogle_google_mymaps">Google Mina Kartor</string>
|
||||
<string name="sendtogoogle_google_docs">Google Dokument</string>
|
||||
<string name="sendtogoogle_send_now">Skicka nu</string>
|
||||
<string name="sendtogoogle_create_new_map">Skapa en ny karta</string>
|
||||
<string name="sendtogoogle_pick_existing_map">Välj en befintlig karta</string>
|
||||
@@ -388,7 +388,7 @@ limitations under the License.
|
||||
<string name="error_rate"> Felfrekvens: %1$d</string>
|
||||
<string name="signal_to_noise_ratio"> Signal / brus-förhållande: %1$d</string>
|
||||
<string name="ecio"> Ec/lo: %1$.1f dB</string>
|
||||
<string name="gsm_strength">GSM styrka: %1$d</string>
|
||||
<string name="cdma_strength">CDMA styrka: %1$d dBm</string>
|
||||
<string name="evdo_strength">EVDO styrka: %1$d dBm</string>
|
||||
<string name="gsm_strength">GSM-styrka: %1$d</string>
|
||||
<string name="cdma_strength">CDMA-styrka: %1$d dBm</string>
|
||||
<string name="evdo_strength">EVDO-styrka: %1$d dBm</string>
|
||||
</resources>
|
||||
|
||||
@@ -147,6 +147,14 @@ limitations under the License.
|
||||
<item>1 分钟</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Auto-resume track timeout -->
|
||||
<string-array name="auto_resume_track_timeout_options">
|
||||
<item>从不</item>
|
||||
<item>5 分钟</item>
|
||||
<item>10 分钟</item>
|
||||
<item>15 分钟</item>
|
||||
<item>总是</item>
|
||||
</string-array>
|
||||
<!-- Other non-preference arrays -->
|
||||
<array name="activity_types">
|
||||
<item>未知</item>
|
||||
|
||||
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<!-- DO NOT TRANSLATE -->
|
||||
<string name="app_name">My Tracks</string>
|
||||
|
||||
|
||||
|
||||
<!-- Used by the Menu -->
|
||||
<string name="start_recording">记录轨迹</string>
|
||||
@@ -32,6 +32,7 @@ limitations under the License.
|
||||
<string name="choose_track_message">请选择要查看的轨迹</string>
|
||||
<string name="current_track">当前轨迹</string>
|
||||
<string name="current_segment">上一段轨迹</string>
|
||||
<string name="aggregated_stats">累积统计</string>
|
||||
|
||||
<!-- Error messages -->
|
||||
<string name="error_no_location_manager">此设备上没有位置管理器!</string>
|
||||
@@ -122,6 +123,7 @@ limitations under the License.
|
||||
<string name="tracklist_save_as_gpx">保存为 GPX 文件</string>
|
||||
<string name="tracklist_save_as_kml">保存为 KML 文件</string>
|
||||
<string name="tracklist_save_as_csv">保存为 CSV 文件</string>
|
||||
<string name="tracklist_save_as_tcx">保存为 TCX 文件</string>
|
||||
<string name="tracklist_btn_send_all_to_mymaps">发送到 Google My Maps</string>
|
||||
<string name="tracklist_btn_delete_all">删除所有轨迹</string>
|
||||
<!-- Export All -->
|
||||
@@ -135,6 +137,7 @@ limitations under the License.
|
||||
<string name="tracklist_share_gpx_file">GPX 文件</string>
|
||||
<string name="tracklist_share_kml_file">KML 文件</string>
|
||||
<string name="tracklist_share_csv_file">CSV 文件</string>
|
||||
<string name="tracklist_share_tcx_file">TCX 文件</string>
|
||||
<string name="tracklist_clear_map">清除地图</string>
|
||||
|
||||
<!-- Waypoints list -->
|
||||
@@ -165,9 +168,10 @@ limitations under the License.
|
||||
<string name="settings_recording">记录</string>
|
||||
<string name="settings_sharing">分享</string>
|
||||
|
||||
<string name="settings_not_available_summary">你使用的 Android 版本无法支持</string>
|
||||
|
||||
<string name="settings_announcement_frequency">提示频度</string>
|
||||
<string name="settings_announcement_frequency_summary">语音提示的最小时间间隔</string>
|
||||
<string name="settings_announcement_not_available_summary">你所使用的 Android 版本不支持文本到语音转换</string>
|
||||
|
||||
<string name="settings_min_recording_distance">最小记录间距</string>
|
||||
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
|
||||
@@ -202,7 +206,38 @@ limitations under the License.
|
||||
|
||||
<string name="settings_split_frequency">切割频度</string>
|
||||
<string name="settings_split_frequency_summary">插入新切割标记所需要达到的最大时间或距离</string>
|
||||
<string name="pace_label">步数</string>
|
||||
|
||||
<string name="settings_auto_resume_track_timeout">自动恢复追踪等待时间</string>
|
||||
<string name="settings_auto_resume_track_timeout_summary">手机重启后自动继续上次轨迹记录的最大间隔时间</string>
|
||||
|
||||
<string name="pace_label">节奏</string>
|
||||
|
||||
<string name="eclair_required">只在 Android 2.0 及以上版本支持</string>
|
||||
<string name="settings_sensor_type">感应器类型</string>
|
||||
<string name="settings_sensor_type_summary">要连接到的感应器类型</string>
|
||||
<string name="settings_sensor_id">感应器ID</string>
|
||||
<string name="settings_sensor_id_summary">发送到感应器的ID.</string>
|
||||
<string name="sensor_id_malformed">ID必须是一个整数值.</string>
|
||||
<string name="bluetooth_sensor_title">蓝牙感应器</string>
|
||||
<string name="bluetooth_sensor_summary">要连接到的蓝牙感应器</string>
|
||||
|
||||
|
||||
|
||||
|
||||
<string name="settings_backup">备份 (测试版)</string>
|
||||
<string name="settings_sd_backup">SD 存储卡备份</string>
|
||||
<string name="settings_backup_to_sd">立即备份</string>
|
||||
<string name="settings_backup_to_sd_summary">将你所有轨迹和设置的完整备份写入到 SD 存储卡</string>
|
||||
<string name="settings_restore_from_sd">立即恢复</string>
|
||||
<string name="settings_restore_from_sd_summary">从 SD 存储卡恢复所有轨迹和设置</string>
|
||||
<string name="settings_no_backup_while_recording">在记录轨迹时无法处理备份数据.</string>
|
||||
|
||||
<!-- Backups -->
|
||||
<string name="restore_overwrites_warning">从 SD 存储卡恢复备份数据将覆盖掉你当前的所有轨迹和设置. 确定要继续吗?</string>
|
||||
<string name="select_backup_to_restore">请选择要恢复的备份文件</string>
|
||||
<string name="no_backups">没有找到备份文件.</string>
|
||||
<string name="backup_write_progress_message">将所有轨迹和设置写入 SD 存储卡中. 这需要一些时间...</string>
|
||||
<string name="backup_import_progress_message">从 SD 存储卡读取轨迹和设置中. 这需要一些时间...</string>
|
||||
|
||||
<!-- MyTracksSky -->
|
||||
<string name="mytracks_sky">GPS 卫星</string>
|
||||
@@ -241,7 +276,7 @@ limitations under the License.
|
||||
|
||||
<!-- Visibility -->
|
||||
<string name="public_map">公开</string>
|
||||
<string name="unlisted_map">Unlisted</string>
|
||||
<string name="unlisted_map">未公布</string>
|
||||
<string name="mapslist_no_maps_available">你还没有保存任何地图.</string>
|
||||
|
||||
<!-- Statistics View -->
|
||||
@@ -292,24 +327,32 @@ limitations under the License.
|
||||
<string name="second">秒</string>
|
||||
<string name="seconds">秒</string>
|
||||
|
||||
|
||||
|
||||
<!-- Distance -->
|
||||
<string name="meter">m</string>
|
||||
<string name="kilometer">km</string>
|
||||
|
||||
|
||||
<string name="kilometers_long">千米</string>
|
||||
<string name="feet">ft</string>
|
||||
<string name="mile">mi</string>
|
||||
|
||||
|
||||
<string name="miles_long">英里</string>
|
||||
|
||||
<!-- Speed and Pace -->
|
||||
<string name="kilometer_per_hour">km/h</string>
|
||||
|
||||
<string name="kilometer_per_hour_long">千米每小时</string>
|
||||
<string name="min_per_kilometer">min/km</string>
|
||||
|
||||
<string name="per_kilometer">每千米</string>
|
||||
<string name="mile_per_hour">mi/h</string>
|
||||
|
||||
<string name="mile_per_hour_long">英里每小时</string>
|
||||
<string name="min_per_mile">min/mi</string>
|
||||
|
||||
<string name="per_mile">每英里</string>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Stats Labels -->
|
||||
<string name="moving_time_label">移动时间</string>
|
||||
<string name="total_time_label">总时间</string>
|
||||
@@ -320,7 +363,11 @@ limitations under the License.
|
||||
<string name="statistics">统计</string>
|
||||
<string name="total_distance_label">总距离</string>
|
||||
<string name="distance_label">距离</string>
|
||||
<string name="unknown">-</string>
|
||||
<string name="power">电源</string>
|
||||
<string name="cadence">节奏</string>
|
||||
<string name="heart_rate">心率</string>
|
||||
<string name="max_heart_rate">最大心率</string>
|
||||
|
||||
<string name="waiting">请稍候...</string>
|
||||
|
||||
<!-- End User License Agreement -->
|
||||
@@ -336,20 +383,27 @@ limitations under the License.
|
||||
<string name="about_email">技术支持</string>
|
||||
<string name="about_support">技术支持:</string>
|
||||
|
||||
<!-- DO NOT TRANSLATE -->
|
||||
<string name="version">1.0.14</string>
|
||||
<string name="about_googlegroups">mytracks@googlegroups.com</string>
|
||||
<string name="about_weburl">mytracks.appspot.com</string>
|
||||
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
|
||||
<string name="about_dylan">Dylan Casey</string>
|
||||
<string name="about_leif">Leif Hendrik Wilden</string>
|
||||
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
|
||||
<string name="about_sandor">Sandor Dornbush</string>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Send to Google -->
|
||||
<string name="sendtogoogle_title">发送到...</string>
|
||||
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
|
||||
<string name="sendtogoogle_google_docs">Google Docs</string>
|
||||
|
||||
|
||||
<string name="sendtogoogle_send_now">立即发送</string>
|
||||
<string name="sendtogoogle_create_new_map">创建新地图</string>
|
||||
<string name="sendtogoogle_pick_existing_map">选择已有地图</string>
|
||||
@@ -364,8 +418,9 @@ limitations under the License.
|
||||
<!-- SD card -->
|
||||
<string name="io_no_external_storage_found">没有发现存贮设备. 请确认插入了 SD 卡并且你的手机没有将挂载 SD 卡到电脑.</string>
|
||||
<string name="io_write_failed">无法写入文件.</string>
|
||||
<string name="io_create_dir_failed">无法在 SD 卡上创建目录.</string>
|
||||
<string name="io_create_dir_failed">无法在 SD 存储卡上创建目录.</string>
|
||||
<string name="io_write_finished">轨迹已导出到 SD 卡.</string>
|
||||
<string name="io_read_finished">从 SD 存储卡读取完毕.</string>
|
||||
|
||||
<!-- VersionChecker -->
|
||||
<string name="version_check_title">检查更新</string>
|
||||
@@ -403,4 +458,29 @@ limitations under the License.
|
||||
<string name="gsm_strength">GSM 强度 %1$d</string>
|
||||
<string name="cdma_strength">CDMA 强度 %1$d dBm</string>
|
||||
<string name="evdo_strength">EVDO 强度 %1$d dBm</string>
|
||||
|
||||
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
|
||||
<string name="method_bluetooth">蓝牙</string>
|
||||
<string name="bluetooth_options">蓝牙设置</string>
|
||||
<string name="bluetooth_pairing">设备关联</string>
|
||||
<string name="bluetooth_pairing_summary">打开 Android 的蓝牙设置关联设备</string>
|
||||
|
||||
<string name="sensor_options">感应器选项</string>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Bluetooth Sensor -->
|
||||
<string name="bluetooth_connected">连接到 %s</string>
|
||||
<string name="bluetooth_connecting">蓝牙感应器连接中 ...</string>
|
||||
<string name="bluetooth_not_connected">未连接蓝牙感应器</string>
|
||||
|
||||
<!-- Sensor State -->
|
||||
<string name="sensor_data_time">感应器数据时间</string>
|
||||
<string name="power_meter_enabled">电源计量器已启用</string>
|
||||
<string name="heart_rate_monitor_enabled">心率监视器已启用</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -236,6 +236,24 @@ limitations under the License.
|
||||
<item>1</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Auto-resume track timeout -->
|
||||
<string-array name="auto_resume_track_timeout_options">
|
||||
<item>Never</item>
|
||||
<item>5 min</item>
|
||||
<item>10 min</item>
|
||||
<item>15 min</item>
|
||||
<item>Always</item>
|
||||
</string-array>
|
||||
|
||||
<!-- DO NOT TRANSLATE -->
|
||||
<string-array name="auto_resume_track_timeout_values">
|
||||
<item>0</item>
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>15</item>
|
||||
<item>-1</item>
|
||||
</string-array>
|
||||
|
||||
|
||||
<!-- Other non-preference arrays -->
|
||||
<array name="activity_types">
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
<resources>
|
||||
<!-- Keys for application preferences - DO NOT TRANSLATE -->
|
||||
<string name="announcement_frequency_key">announcementFrequency</string>
|
||||
<string name="auto_resume_track_timeout_key">autoResumeTrackTimeout</string>
|
||||
<string name="bluetooth_device_key">bluetoothDevice</string>
|
||||
<string name="bluetooth_pairing_key">bluetoothPairing</string>
|
||||
<string name="bluetooth_sensor_key">bluetoothSensor</string>
|
||||
@@ -37,12 +38,14 @@ limitations under the License.
|
||||
<string name="share_url_only_key">shareUrlOnly</string>
|
||||
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
||||
<string name="split_frequency_key">splitFrequency</string>
|
||||
<string name="timestamp_track_name_key">timestampTrackName</string>
|
||||
|
||||
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
|
||||
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
|
||||
<string name="recording_track_key">recordingTrack</string>
|
||||
<string name="selected_track_key">selectedTrack</string>
|
||||
|
||||
<!-- Keys for non-persisted preferences - DO NOT TRANSLATE -->
|
||||
<string name="backup_to_sd_key">backupToSd</string>
|
||||
<string name="restore_from_sd_key">restoreFromSd</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2010 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<array name="left_icons">
|
||||
<item>@drawable/left_arrow_chart_stateful</item>
|
||||
<item>@drawable/left_arrow_track_stateful</item>
|
||||
<item>@drawable/left_arrow_stats_stateful</item>
|
||||
</array>
|
||||
|
||||
<array name="right_icons">
|
||||
<item>@drawable/right_arrow_stats_stateful</item>
|
||||
<item>@drawable/right_arrow_chart_stateful</item>
|
||||
<item>@drawable/right_arrow_track_stateful</item>
|
||||
</array>
|
||||
</resources>
|
||||
@@ -32,6 +32,7 @@ limitations under the License.
|
||||
<string name="choose_track_message">Please select the track to view</string>
|
||||
<string name="current_track">Current track</string>
|
||||
<string name="current_segment">Last segment</string>
|
||||
<string name="aggregated_stats">Aggregated Statistics</string>
|
||||
|
||||
<!-- Error messages -->
|
||||
<string name="error_no_location_manager">No location manager was found on this device!</string>
|
||||
@@ -200,11 +201,19 @@ limitations under the License.
|
||||
<string name="settings_share_url_only_summary_on">Only the URL of the map will be sent when sharing</string>
|
||||
<string name="settings_share_url_only_summary_off">The contents of the track will also be included when sharing</string>
|
||||
|
||||
<string name="settings_signal_sampling_frequency">Signal Sampling frequency</string>
|
||||
<string name="settings_signal_sampling_frequency">Signal sampling frequency</string>
|
||||
<string name="settings_signal_sampling_frequency_summary">Time between two recordings of the phone\'s signal strength</string>
|
||||
|
||||
<string name="settings_split_frequency">Split Frequency</string>
|
||||
<string name="settings_split_frequency">Split frequency</string>
|
||||
<string name="settings_split_frequency_summary">Maximum time or distance after which a new split marker will be inserted</string>
|
||||
|
||||
<string name="settings_auto_resume_track_timeout">Auto-resume track timeout</string>
|
||||
<string name="settings_auto_resume_track_timeout_summary">Maximum time in minutes after which a previously recording track will not be resumed on phone reboot</string>
|
||||
|
||||
<string name="settings_timestamp_track_name">Timestamp as track name</string>
|
||||
<string name="settings_timestamp_track_name_summary_on">The default track name will be the date and time at the start of the track</string>
|
||||
<string name="settings_timestamp_track_name_summary_off">Track names will have numbers which increment for each new track.</string>
|
||||
|
||||
<string name="pace_label">Pace</string>
|
||||
|
||||
<string name="eclair_required">Only supported in Android 2.0 or above</string>
|
||||
|
||||
@@ -91,6 +91,22 @@ limitations under the License.
|
||||
android:entries="@array/task_frequency_options"
|
||||
android:entryValues="@array/task_frequency_values"
|
||||
android:defaultValue="-1" />
|
||||
|
||||
<com.google.android.apps.mytracks.IntegerListPreference
|
||||
android:key="@string/auto_resume_track_timeout_key"
|
||||
android:title="@string/settings_auto_resume_track_timeout"
|
||||
android:summary="@string/settings_auto_resume_track_timeout_summary"
|
||||
android:entries="@array/auto_resume_track_timeout_options"
|
||||
android:entryValues="@array/auto_resume_track_timeout_values"
|
||||
android:defaultValue="10" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="@string/timestamp_track_name_key"
|
||||
android:title="@string/settings_timestamp_track_name"
|
||||
android:summaryOn="@string/settings_timestamp_track_name_summary_on"
|
||||
android:summaryOff="@string/settings_timestamp_track_name_summary_off"
|
||||
android:defaultValue="true" />
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
<!-- Sharing settings -->
|
||||
@@ -118,7 +134,7 @@ limitations under the License.
|
||||
android:title="@string/settings_backup_to_sd"
|
||||
android:summary="@string/settings_backup_to_sd_summary"
|
||||
android:persistent="false" />
|
||||
<Preference
|
||||
<Preference
|
||||
android:key="@string/restore_from_sd_key"
|
||||
android:title="@string/settings_restore_from_sd"
|
||||
android:summary="@string/settings_restore_from_sd_summary"
|
||||
|
||||
@@ -25,5 +25,8 @@ public class MyMapsConstants {
|
||||
"http://maps.google.com/maps/ms";
|
||||
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
*/
|
||||
private MyMapsConstants() { }
|
||||
}
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mymaps;
|
||||
|
||||
import com.google.android.apps.mymaps.MyMapsGDataWrapper.QueryFunction;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
import com.google.wireless.gdata.GDataException;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.data.Entry;
|
||||
import com.google.wireless.gdata.maps.MapsClient;
|
||||
import com.google.wireless.gdata.parser.GDataParser;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.TabActivity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class VersionChecker {
|
||||
/** The User id for the account storing MyTracks versions. */
|
||||
private static final String VERSION_USER_ID = "116927647071651051525";
|
||||
private static final String VERSION_CLIENT = "AndroidMyTracks";
|
||||
|
||||
private static final String VERSION_STATUS_LATEST = "latest";
|
||||
private static final String VERSION_STATUS_OKAY = "okay";
|
||||
private static final String VERSION_STATUS_OLD = "old";
|
||||
private static final String VERSION_STATUS_BAD = "bad";
|
||||
|
||||
private final TabActivity parentActivity;
|
||||
private Dialog dialog;
|
||||
private boolean canceledAvailable;
|
||||
private boolean canceledRecommended;
|
||||
|
||||
public VersionChecker(TabActivity parent) {
|
||||
parentActivity = parent;
|
||||
canceledAvailable = false;
|
||||
canceledRecommended = false;
|
||||
|
||||
startMapsQueryThread();
|
||||
}
|
||||
|
||||
private void startMapsQueryThread() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
MyMapsGDataWrapper mapsGDataWrapper =
|
||||
new MyMapsGDataWrapper(parentActivity);
|
||||
mapsGDataWrapper.runQuery(new QueryFunction() {
|
||||
@Override
|
||||
public void query(MapsClient client) throws IOException, Exception {
|
||||
doMapsQuery(client);
|
||||
}
|
||||
});
|
||||
mapsGDataWrapper.cleanUp();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void doMapsQuery(MapsClient client)
|
||||
throws GDataException, IOException, HttpException {
|
||||
final boolean newVersion; // Returned new version available
|
||||
final boolean newVersionRecommended; // Returned new version recommended
|
||||
final boolean newVersionRequired; // Returned new version required
|
||||
String currentVersion = "";
|
||||
try {
|
||||
PackageInfo pi =
|
||||
parentActivity.getPackageManager().getPackageInfo(
|
||||
"com.google.android.maps.mytracks",
|
||||
PackageManager.GET_META_DATA);
|
||||
currentVersion = pi.versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
Log.w(MyTracksConstants.TAG, "Failed to get version info.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GDataParser parser = client.getParserForFeed(
|
||||
Entry.class,
|
||||
MapsClient.getVersionFeed(
|
||||
VERSION_USER_ID,
|
||||
VERSION_CLIENT,
|
||||
currentVersion),
|
||||
"");
|
||||
Entry en = parser.parseStandaloneEntry();
|
||||
if (currentVersion.equals(en.getTitle())) {
|
||||
if (VERSION_STATUS_LATEST.equals(en.getContent())) {
|
||||
newVersion = false;
|
||||
newVersionRecommended = false;
|
||||
newVersionRequired = false;
|
||||
} else if (VERSION_STATUS_OKAY.equals(en.getContent())) {
|
||||
newVersion = true;
|
||||
newVersionRecommended = false;
|
||||
newVersionRequired = false;
|
||||
} else if (VERSION_STATUS_OLD.equals(en.getContent())) {
|
||||
newVersion = true;
|
||||
newVersionRecommended = true;
|
||||
newVersionRequired = false;
|
||||
} else if (VERSION_STATUS_BAD.equals(en.getContent())) {
|
||||
newVersion = true;
|
||||
newVersionRecommended = true;
|
||||
newVersionRequired = true;
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"Got unknown version status: " + en.getContent());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"Current version '" + currentVersion + "' doesn't match entry: "
|
||||
+ en.getTitle());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newVersion) {
|
||||
Log.d(MyTracksConstants.TAG, "No new version available.");
|
||||
return;
|
||||
}
|
||||
|
||||
final AlertDialog.Builder dialogBuilder =
|
||||
new AlertDialog.Builder(parentActivity);
|
||||
dialogBuilder.setTitle(R.string.version_check_title);
|
||||
dialogBuilder.setPositiveButton(R.string.version_check_upgrade_button,
|
||||
new OnClickListener() {
|
||||
public void onClick(DialogInterface di, int number) {
|
||||
Intent upgradeIntent = new Intent();
|
||||
upgradeIntent.setAction(Intent.ACTION_VIEW);
|
||||
upgradeIntent.setData(Uri.parse(String.format("market://search?q=%s",
|
||||
parentActivity.getResources().getString(
|
||||
R.string.app_name).toString())));
|
||||
parentActivity.startActivity(upgradeIntent);
|
||||
parentActivity.finish();
|
||||
}
|
||||
});
|
||||
|
||||
if (newVersionRequired) {
|
||||
dialogBuilder.setCancelable(false);
|
||||
} else {
|
||||
final boolean recommended = newVersionRecommended;
|
||||
dialogBuilder.setNegativeButton(R.string.version_check_continue_button,
|
||||
new OnClickListener() {
|
||||
public void onClick(DialogInterface di, int number) {
|
||||
dialog.dismiss();
|
||||
canceledAvailable = true;
|
||||
if (recommended) {
|
||||
canceledRecommended = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int messageResource;
|
||||
if (newVersionRequired) {
|
||||
messageResource = R.string.upgrade_required;
|
||||
} else if (newVersionRecommended) {
|
||||
messageResource = R.string.upgrade_recommended;
|
||||
if (canceledRecommended) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
messageResource = R.string.upgrade_available;
|
||||
if (canceledAvailable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String message = parentActivity.getResources().getString(messageResource);
|
||||
dialogBuilder.setMessage(String.format(message,
|
||||
parentActivity.getResources().getString(R.string.app_name)));
|
||||
parentActivity.getTabHost().post(new Runnable() {
|
||||
public void run() {
|
||||
dialog = dialogBuilder.create();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
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.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Activity for viewing the combined statistics for all the recorded tracks.
|
||||
*
|
||||
* Other features to add - menu items to change setings.
|
||||
*
|
||||
* @author Fergus Nelson
|
||||
*/
|
||||
public class AggregatedStatsActivity extends Activity implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
|
||||
private final StatsUtilities utils;
|
||||
|
||||
private MyTracksProviderUtils tracksProvider;
|
||||
|
||||
private boolean metricUnits = true;
|
||||
|
||||
public AggregatedStatsActivity() {
|
||||
this.utils = new StatsUtilities(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
Log.d(MyTracksConstants.TAG, "StatsActivity: onSharedPreferences changed "
|
||||
+ key);
|
||||
if (key != null) {
|
||||
if (key.equals(R.string.metric_units_key)) {
|
||||
metricUnits = sharedPreferences.getBoolean(
|
||||
getString(R.string.metric_units_key), true);
|
||||
utils.setMetricUnits(metricUnits);
|
||||
utils.updateUnits();
|
||||
loadAggregatedStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
this.tracksProvider = MyTracksProviderUtils.Factory.get(this);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.stats);
|
||||
|
||||
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
|
||||
sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
|
||||
|
||||
SharedPreferences preferences = getSharedPreferences(
|
||||
MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (preferences != null) {
|
||||
metricUnits = preferences.getBoolean(getString(R.string.metric_units_key), true);
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
utils.setMetricUnits(metricUnits);
|
||||
utils.updateUnits();
|
||||
utils.setSpeedLabel(R.id.speed_label, R.string.speed, R.string.pace_label);
|
||||
utils.setSpeedLabels();
|
||||
|
||||
DisplayMetrics metrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(metrics);
|
||||
if (metrics.heightPixels > 600) {
|
||||
((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f);
|
||||
}
|
||||
loadAggregatedStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Reads tracks from the db
|
||||
* 2. Merges the trip stats from the tracks
|
||||
* 3. Updates the view
|
||||
*/
|
||||
private void loadAggregatedStats() {
|
||||
List<Track> tracks = retrieveTracks();
|
||||
TripStatistics rollingStats = null;
|
||||
if (!tracks.isEmpty()) {
|
||||
rollingStats = new TripStatistics(tracks.iterator().next()
|
||||
.getStatistics());
|
||||
for (int i = 1; i < tracks.size(); i++) {
|
||||
rollingStats.merge(tracks.get(i).getStatistics());
|
||||
}
|
||||
}
|
||||
updateView(rollingStats);
|
||||
}
|
||||
|
||||
private List<Track> retrieveTracks() {
|
||||
return tracksProvider.getAllTracks();
|
||||
}
|
||||
|
||||
private void updateView(TripStatistics aggStats) {
|
||||
if (aggStats != null) {
|
||||
utils.setAllStats(aggStats);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,10 @@ import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
@@ -35,7 +37,6 @@ import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
@@ -176,11 +177,15 @@ public class ChartActivity extends Activity implements
|
||||
final int idColumnIdx =
|
||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||
ArrayList<double[]> data = new ArrayList<double[]>();
|
||||
// Need two locations so we can keep track of the last location.
|
||||
Location location = new Location("");
|
||||
do {
|
||||
lastSeenLocationId = cursor.getLong(idColumnIdx);
|
||||
Location location = providerUtils.createLocation(cursor);
|
||||
if (location != null && MyTracksUtils.isValidLocation(location)) {
|
||||
data.add(getDataPoint(location, track));
|
||||
providerUtils.fillLocation(cursor, location);
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
double[] point = new double[3];
|
||||
location = getDataPoint(location, track, point);
|
||||
data.add(point);
|
||||
}
|
||||
} while (cursor.moveToPrevious());
|
||||
cv.addDataPoints(data);
|
||||
@@ -239,7 +244,9 @@ public class ChartActivity extends Activity implements
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.mytracks_elevation);
|
||||
@@ -291,13 +298,13 @@ public class ChartActivity extends Activity implements
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
|
||||
// Check for any new locations and append them to the currently
|
||||
// recording track:
|
||||
if (!MyTracks.getInstance().isRecording()) {
|
||||
// recording track.
|
||||
if (recordingTrackId < 0) {
|
||||
// No track is being recorded. We should not be here.
|
||||
return;
|
||||
}
|
||||
if (selectedTrackId != recordingTrackId) {
|
||||
// no track, or one other than the recording track is selected, don't
|
||||
// No track, or one other than the recording track is selected, don't
|
||||
// bother.
|
||||
return;
|
||||
}
|
||||
@@ -448,7 +455,8 @@ public class ChartActivity extends Activity implements
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case MyTracksConstants.MENU_CHART_SETTINGS:
|
||||
MyTracks.getInstance().showDialogSafely(MyTracks.DIALOG_CHART_SETTINGS);
|
||||
MyTracks.getInstance().getDialogManager().showDialogSafely(
|
||||
DialogManager.DIALOG_CHART_SETTINGS);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
@@ -461,11 +469,14 @@ public class ChartActivity extends Activity implements
|
||||
* data[1] = the elevation
|
||||
* data[2] = the speed
|
||||
*
|
||||
* @param location a location
|
||||
* @return the data point
|
||||
* This must be called in order for each point.
|
||||
*
|
||||
* @param location the location to get data for (this method takes ownership of that location)
|
||||
* @param track the track to get data from
|
||||
* @param result the resulting point to fill out
|
||||
* @return the previous location, now available for reuse
|
||||
*/
|
||||
public double[] getDataPoint(Location location, Track track) {
|
||||
double[] result = new double[3];
|
||||
private Location getDataPoint(Location location, Track track, double[] result) {
|
||||
switch (mode) {
|
||||
case BY_DISTANCE:
|
||||
result[0] = profileLength;
|
||||
@@ -512,8 +523,16 @@ public class ChartActivity extends Activity implements
|
||||
// Format as hours per unit
|
||||
result[2] = (60.0 / result[2]);
|
||||
}
|
||||
|
||||
Location oldLastLocation = lastLocation;
|
||||
lastLocation = location;
|
||||
return result;
|
||||
|
||||
if (oldLastLocation == null) {
|
||||
// No previous location, but return a blank one for reuse
|
||||
return new Location("");
|
||||
}
|
||||
|
||||
return oldLastLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -556,6 +575,8 @@ public class ChartActivity extends Activity implements
|
||||
try {
|
||||
final ArrayList<double[]> theData = new ArrayList<double[]>();
|
||||
int points = 0;
|
||||
// Need two locations so we can keep track of the last location.
|
||||
Location location = new Location("");
|
||||
while (lastLocationRead < track.getStopId()) {
|
||||
cursor = providerUtils.getLocationsCursor(
|
||||
selectedTrackId, lastLocationRead, bufferSize, false);
|
||||
@@ -567,11 +588,12 @@ public class ChartActivity extends Activity implements
|
||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||
while (cursor.moveToNext()) {
|
||||
points++;
|
||||
Location location = providerUtils.createLocation(cursor);
|
||||
providerUtils.fillLocation(cursor, location);
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
lastLocationRead = lastSeenLocationId =
|
||||
cursor.getLong(idColumnIdx);
|
||||
double[] point = getDataPoint(location, track);
|
||||
double[] point = new double[3];
|
||||
location = getDataPoint(location, track, point);
|
||||
if (points % chartSamplingFrequency == 0) {
|
||||
theData.add(point);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.graphics.Path;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* This class encasulates meta data about one series of values for a chart.
|
||||
* This class encapsulates meta data about one series of values for a chart.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.BadTokenException;
|
||||
|
||||
/**
|
||||
* A class to handle all dialog related events for My Tracks.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class DialogManager {
|
||||
|
||||
public static final int DIALOG_CHART_SETTINGS = 1;
|
||||
public static final int DIALOG_IMPORT_PROGRESS = 2;
|
||||
public static final int DIALOG_PROGRESS = 3;
|
||||
public static final int DIALOG_SEND_TO_GOOGLE = 4;
|
||||
public static final int DIALOG_SEND_TO_GOOGLE_RESULT = 5;
|
||||
public static final int DIALOG_WRITE_PROGRESS = 6;
|
||||
|
||||
private ProgressDialog progressDialog;
|
||||
private ProgressDialog importProgressDialog;
|
||||
private ProgressDialog writeProgressDialog;
|
||||
private SendToGoogleDialog sendToGoogleDialog;
|
||||
private AlertDialog sendToGoogleResultDialog;
|
||||
private ChartSettingsDialog chartSettingsDialog;
|
||||
|
||||
private MyTracks activity;
|
||||
|
||||
public DialogManager(MyTracks activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
protected Dialog onCreateDialog(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case DIALOG_CHART_SETTINGS:
|
||||
chartSettingsDialog = new ChartSettingsDialog(activity);
|
||||
return chartSettingsDialog;
|
||||
case DIALOG_IMPORT_PROGRESS:
|
||||
importProgressDialog = new ProgressDialog(activity);
|
||||
importProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
importProgressDialog.setTitle(
|
||||
activity.getString(R.string.progress_title));
|
||||
importProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
importProgressDialog.setMessage(
|
||||
activity.getString(R.string.import_progress_message));
|
||||
return importProgressDialog;
|
||||
case DIALOG_PROGRESS:
|
||||
progressDialog = new ProgressDialog(activity);
|
||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progressDialog.setTitle(activity.getString(R.string.progress_title));
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setMessage("");
|
||||
progressDialog.setMax(100);
|
||||
progressDialog.setProgress(10);
|
||||
return progressDialog;
|
||||
case DIALOG_SEND_TO_GOOGLE:
|
||||
sendToGoogleDialog = new SendToGoogleDialog(activity);
|
||||
return sendToGoogleDialog;
|
||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
||||
builder.setTitle("Title");
|
||||
builder.setMessage("Message");
|
||||
builder.setPositiveButton(activity.getString(R.string.ok), null);
|
||||
builder.setNeutralButton(activity.getString(R.string.share_map),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
activity.shareLinkToMyMap(activity.getSendToMyMapsMapId());
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
sendToGoogleResultDialog = builder.create();
|
||||
return sendToGoogleResultDialog;
|
||||
case DIALOG_WRITE_PROGRESS:
|
||||
writeProgressDialog = new ProgressDialog(activity);
|
||||
writeProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||
writeProgressDialog.setTitle(
|
||||
activity.getString(R.string.progress_title));
|
||||
writeProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
writeProgressDialog.setMessage(
|
||||
activity.getString(R.string.write_progress_message));
|
||||
return writeProgressDialog;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||
switch (id) {
|
||||
case DIALOG_SEND_TO_GOOGLE:
|
||||
activity.resetSendToGoogleStatus();
|
||||
break;
|
||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
||||
boolean success = activity.getSendToGoogleSuccess();
|
||||
sendToGoogleResultDialog.setTitle(
|
||||
success ? R.string.success : R.string.error);
|
||||
sendToGoogleResultDialog.setIcon(success
|
||||
? android.R.drawable.ic_dialog_info
|
||||
: android.R.drawable.ic_dialog_alert);
|
||||
sendToGoogleResultDialog.setMessage(activity.getMapsResultMessage());
|
||||
|
||||
boolean canShare = activity.getSendToMyMapsMapId() != null;
|
||||
View share =
|
||||
sendToGoogleResultDialog.findViewById(android.R.id.button3);
|
||||
if (share != null) {
|
||||
share.setVisibility(canShare ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
break;
|
||||
case DIALOG_CHART_SETTINGS:
|
||||
Log.d(MyTracksConstants.TAG, "MyTracks.onPrepare chart dialog");
|
||||
chartSettingsDialog.setup(activity.getChartActivity());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressMessage(final String message) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
synchronized (this) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setProgressValue(final int percent) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
synchronized (this) {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.setProgress(percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sendToGoogleDialog
|
||||
*/
|
||||
public SendToGoogleDialog getSendToGoogleDialog() {
|
||||
return sendToGoogleDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog with the given message.
|
||||
* Does it on the UI thread.
|
||||
*
|
||||
* @param success if true, displays an info icon/title, otherwise an error
|
||||
* icon/title
|
||||
* @param message resource string id
|
||||
*/
|
||||
public void showMessageDialog(final int message, final boolean success) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
AlertDialog dialog = null;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setMessage(activity.getString(message));
|
||||
builder.setNegativeButton(activity.getString(R.string.ok), null);
|
||||
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
|
||||
android.R.drawable.ic_dialog_alert);
|
||||
builder.setTitle(success ? R.string.success : R.string.error);
|
||||
dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like showDialog, but will catch a BadTokenException that sometimes
|
||||
* (very rarely) gets thrown. This might happen if the user hits the "back"
|
||||
* button immediately after sending tracks to google.
|
||||
*
|
||||
* @param id the dialog id
|
||||
*/
|
||||
public void showDialogSafely(final int id) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
activity.showDialog(id);
|
||||
} catch (BadTokenException e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Could not display dialog with id " + id, e);
|
||||
} catch (IllegalStateException e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Could not display dialog with id " + id, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the progress dialog if it is showing. Executed on the UI thread.
|
||||
*/
|
||||
public void dismissDialogSafely(final int id) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
activity.dismissDialog(id);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This will be thrown if this dialog was not shown before.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
@@ -46,7 +47,7 @@ public class ExportAllTracks {
|
||||
public static final int KML_OPTION_INDEX = 1;
|
||||
public static final int CSV_OPTION_INDEX = 2;
|
||||
|
||||
private final Context context;
|
||||
private final Activity activity;
|
||||
private WakeLock wakeLock;
|
||||
private ProgressDialog progress;
|
||||
|
||||
@@ -70,11 +71,11 @@ public class ExportAllTracks {
|
||||
}
|
||||
};
|
||||
|
||||
public ExportAllTracks(Context c) {
|
||||
this.context = c;
|
||||
public ExportAllTracks(Activity activity) {
|
||||
this.activity = activity;
|
||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Starting");
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(c);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
|
||||
builder.setPositiveButton(R.string.ok, positiveClick);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
@@ -107,11 +108,11 @@ public class ExportAllTracks {
|
||||
*/
|
||||
private void aquireLocksAndExport() {
|
||||
SharedPreferences prefs =
|
||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
long recordingTrackId = -1;
|
||||
if (prefs != null) {
|
||||
recordingTrackId =
|
||||
prefs.getLong(context.getString(R.string.recording_track_key), -1);
|
||||
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
|
||||
}
|
||||
if (recordingTrackId != -1) {
|
||||
acquireWakeLock();
|
||||
@@ -127,12 +128,12 @@ public class ExportAllTracks {
|
||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
|
||||
}
|
||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Done");
|
||||
Toast.makeText(context, R.string.export_done, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void makeProgressDialog(final int trackCount) {
|
||||
String exportMsg = context.getString(R.string.tracklist_btn_export_all);
|
||||
progress = new ProgressDialog(context);
|
||||
String exportMsg = activity.getString(R.string.tracklist_btn_export_all);
|
||||
progress = new ProgressDialog(activity);
|
||||
progress.setIcon(android.R.drawable.ic_dialog_info);
|
||||
progress.setTitle(exportMsg);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
@@ -150,7 +151,7 @@ public class ExportAllTracks {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
MyTracksProviderUtils providerUtils =
|
||||
MyTracksProviderUtils.Factory.get(context);
|
||||
MyTracksProviderUtils.Factory.get(activity);
|
||||
cursor = providerUtils.getTracksCursor("");
|
||||
if (cursor == null) {
|
||||
return;
|
||||
@@ -160,7 +161,7 @@ public class ExportAllTracks {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
|
||||
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
makeProgressDialog(trackCount);
|
||||
}
|
||||
@@ -168,7 +169,7 @@ public class ExportAllTracks {
|
||||
|
||||
for (int i = 0; cursor.moveToNext(); i++) {
|
||||
final int status = i;
|
||||
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
synchronized (this) {
|
||||
if (progress == null) {
|
||||
@@ -182,13 +183,13 @@ public class ExportAllTracks {
|
||||
long id = cursor.getLong(idxTrackId);
|
||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
|
||||
TrackWriter writer =
|
||||
TrackWriterFactory.newWriter(context, providerUtils, id, format);
|
||||
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
||||
writer.writeTrack();
|
||||
|
||||
if (!writer.wasSuccess()) {
|
||||
// Abort the whole export on the first error.
|
||||
int error = writer.getErrorMessage();
|
||||
Toast.makeText(context, error, Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -213,7 +214,7 @@ public class ExportAllTracks {
|
||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
|
||||
try {
|
||||
PowerManager pm =
|
||||
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
(PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"ExportAllTracks: Power manager not found!");
|
||||
|
||||
@@ -28,7 +28,7 @@ import android.view.MenuItem;
|
||||
*/
|
||||
class MenuManager {
|
||||
|
||||
private MyTracks activity;
|
||||
private final MyTracks activity;
|
||||
|
||||
public MenuManager(MyTracks activity) {
|
||||
this.activity = activity;
|
||||
@@ -44,12 +44,12 @@ class MenuManager {
|
||||
menu.findItem(R.id.menu_list_tracks).setEnabled(hasRecorded);
|
||||
menu.findItem(R.id.menu_list_markers)
|
||||
.setEnabled(hasRecorded && hasSelectedTrack);
|
||||
MenuItem startRecording = menu.findItem(R.id.menu_start_recording);
|
||||
startRecording.setEnabled(!isRecording);
|
||||
startRecording.setVisible(!isRecording);
|
||||
MenuItem stopRecording = menu.findItem(R.id.menu_stop_recording);
|
||||
stopRecording.setEnabled(isRecording);
|
||||
stopRecording.setVisible(isRecording);
|
||||
menu.findItem(R.id.menu_start_recording)
|
||||
.setEnabled(!isRecording)
|
||||
.setVisible(!isRecording);
|
||||
menu.findItem(R.id.menu_stop_recording)
|
||||
.setEnabled(isRecording)
|
||||
.setVisible(isRecording);
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
@@ -69,7 +69,7 @@ class MenuManager {
|
||||
}
|
||||
case R.id.menu_list_markers: {
|
||||
Intent startIntent = new Intent(activity, MyTracksWaypointsList.class);
|
||||
startIntent.putExtra("trackid", activity.getSelectedTrack());
|
||||
startIntent.putExtra("trackid", activity.getSelectedTrackId());
|
||||
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
|
||||
return true;
|
||||
}
|
||||
@@ -78,13 +78,18 @@ class MenuManager {
|
||||
activity.startActivity(startIntent);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_aggregated_stats: {
|
||||
Intent startIntent = new Intent(activity, AggregatedStatsActivity.class);
|
||||
activity.startActivity(startIntent);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_help: {
|
||||
Intent startIntent = new Intent(activity, WelcomeActivity.class);
|
||||
activity.startActivity(startIntent);
|
||||
return true;
|
||||
}
|
||||
case MyTracksConstants.MENU_CLEAR_MAP: {
|
||||
activity.setSelectedTrack(-1);
|
||||
activity.setSelectedTrackId(-1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Constants used by the MyTracks application.
|
||||
*
|
||||
@@ -40,11 +38,6 @@ public abstract class MyTracksConstants {
|
||||
*/
|
||||
public static final String SDCARD_TOP_DIR = "MyTracks";
|
||||
|
||||
/**
|
||||
* The API level of the Android version we're being run under.
|
||||
*/
|
||||
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
||||
|
||||
/*
|
||||
* onActivityResult request codes:
|
||||
*/
|
||||
@@ -179,6 +172,13 @@ public abstract class MyTracksConstants {
|
||||
* The type of account that we can use for gdata uploads.
|
||||
*/
|
||||
public static final String ACCOUNT_TYPE = "com.google";
|
||||
|
||||
/**
|
||||
* The name of extra intent property to indicate whether we want to resume
|
||||
* a previously recorded track.
|
||||
*/
|
||||
public static final String RESUME_TRACK_EXTRA_NAME =
|
||||
"com.google.android.apps.mytracks.RESUME_TRACK";
|
||||
|
||||
public static int getActionFromMenuId(int menuId) {
|
||||
switch (menuId) {
|
||||
|
||||
@@ -21,7 +21,9 @@ import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapActivity;
|
||||
@@ -47,17 +49,16 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.provider.Settings;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.View.OnCreateContextMenuListener;
|
||||
import android.view.Window;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.View.OnCreateContextMenuListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
@@ -70,8 +71,9 @@ import android.widget.Toast;
|
||||
* @author Leif Hendrik Wilden
|
||||
*/
|
||||
public class MyTracksMap extends MapActivity
|
||||
implements LocationListener, SensorEventListener, View.OnTouchListener,
|
||||
View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
implements View.OnTouchListener, View.OnClickListener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final int TRACKPOINT_BUFFER_SIZE = 1024;
|
||||
|
||||
// Saved instance state keys:
|
||||
// ---------------------------
|
||||
@@ -82,9 +84,9 @@ public class MyTracksMap extends MapActivity
|
||||
public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix";
|
||||
|
||||
/**
|
||||
* The currently selected track (or null if nothing selected).
|
||||
* The ID of the currently selected track (or -1 if nothing selected).
|
||||
*/
|
||||
private Track selectedTrack;
|
||||
private long selectedTrackId = -1;
|
||||
|
||||
/**
|
||||
* The id of the currently recording track.
|
||||
@@ -97,20 +99,26 @@ public class MyTracksMap extends MapActivity
|
||||
*/
|
||||
private boolean keepMyLocationVisible;
|
||||
|
||||
/**
|
||||
* Id of the first location that was seen when reading tracks from the
|
||||
* provider.
|
||||
*/
|
||||
private long firstSeenLocationId = -1;
|
||||
|
||||
/**
|
||||
* Id of the last location that was seen when reading tracks from the
|
||||
* provider. This is used to determine which locations are new compared to the
|
||||
* last time the mapOverlay was updated.
|
||||
*/
|
||||
private long lastSeenLocationId = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Magnetic variation.
|
||||
*/
|
||||
private double variation;
|
||||
|
||||
/**
|
||||
* From the shared preferences:
|
||||
* From the shared preferences.
|
||||
*/
|
||||
private int minRequiredAccuracy =
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
||||
@@ -127,32 +135,31 @@ public class MyTracksMap extends MapActivity
|
||||
private Location currentLocation;
|
||||
|
||||
/**
|
||||
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
|
||||
* this thread.
|
||||
* A thread with a looper. Post to updateTrackHandler to execute
|
||||
* {@link Runnable}s on this thread.
|
||||
*/
|
||||
private final HandlerThread updateTrackThread =
|
||||
new HandlerThread("updateTrackThread");
|
||||
private HandlerThread updateTrackThread;
|
||||
|
||||
/** Handler for updateTrackThread */
|
||||
/**
|
||||
* Handler for updateTrackThread.
|
||||
*/
|
||||
private Handler updateTrackHandler;
|
||||
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
/**
|
||||
* This value is used to decide how many points to display.
|
||||
*/
|
||||
private int samplingFrequency = 1;
|
||||
|
||||
/**
|
||||
* A runnable that updates the track from the provider (looking for points
|
||||
* added after "lastSeenLocationId".
|
||||
* added after "lastSeenLocationId").
|
||||
*/
|
||||
private final Runnable updateTrackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (selectedTrack == null) {
|
||||
if (!isATrackSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
readAllNewTrackPoints();
|
||||
}
|
||||
};
|
||||
@@ -163,11 +170,13 @@ public class MyTracksMap extends MapActivity
|
||||
private Runnable restoreTrackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (selectedTrack == null) {
|
||||
if (!isATrackSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapOverlay.clearPoints();
|
||||
lastSeenLocationId = selectedTrack.getStartId();
|
||||
firstSeenLocationId = -1;
|
||||
lastSeenLocationId = -1;
|
||||
readAllNewTrackPoints();
|
||||
}
|
||||
};
|
||||
@@ -178,7 +187,7 @@ public class MyTracksMap extends MapActivity
|
||||
private final Runnable restoreWaypointsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (selectedTrack == null) {
|
||||
if (!isATrackSelected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -188,15 +197,12 @@ public class MyTracksMap extends MapActivity
|
||||
// We will silently drop extra waypoints to make the app responsive.
|
||||
// TODO: Try to only load the waypoints in the view port.
|
||||
cursor = providerUtils.getWaypointsCursor(
|
||||
selectedTrack.getId(), 0,
|
||||
selectedTrackId, 0,
|
||||
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||
mapOverlay.addWaypoint(wpt);
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
do {
|
||||
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||
@@ -210,19 +216,18 @@ public class MyTracksMap extends MapActivity
|
||||
};
|
||||
|
||||
/**
|
||||
* A runnable intended to be posted to the updateTrackThread after the
|
||||
* selected track changes. It will post to the ui thread to update the screen
|
||||
* elements and move the map to show the selected track.
|
||||
* A runnable intended to be posted to the {@code #updateTrackThread} after
|
||||
* the selected track changes. It will post to the UI thread to update
|
||||
* the screen elements and move the map to show the selected track.
|
||||
*/
|
||||
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
uiHandler.post(new Runnable() {
|
||||
public void run() {
|
||||
showTrack(selectedTrack);
|
||||
mapOverlay.setSelectedTrack(selectedTrack);
|
||||
mapOverlay.setShowEndMarker(selectedTrack == null ||
|
||||
selectedTrack.getId() != recordingTrackId);
|
||||
showTrack(selectedTrackId);
|
||||
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
||||
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
||||
mapView.invalidate();
|
||||
busyPane.setVisibility(View.GONE);
|
||||
updateOptionsButton();
|
||||
@@ -281,7 +286,9 @@ public class MyTracksMap extends MapActivity
|
||||
super.onCreate(bundle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
|
||||
@@ -316,6 +323,7 @@ public class MyTracksMap extends MapActivity
|
||||
locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
|
||||
updateTrackThread = new HandlerThread("updateTrackThread");
|
||||
updateTrackThread.start();
|
||||
updateTrackHandler = new Handler(updateTrackThread.getLooper());
|
||||
|
||||
@@ -325,14 +333,7 @@ public class MyTracksMap extends MapActivity
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
|
||||
// Check for any new locations and append them to the currently
|
||||
// recording track:
|
||||
if (!MyTracks.getInstance().isRecording()) {
|
||||
// No track is being recorded. We should not be here.
|
||||
return;
|
||||
}
|
||||
if (selectedTrack == null
|
||||
|| selectedTrack.getId() != recordingTrackId) {
|
||||
if (!isRecordingSelected()) {
|
||||
// No track, or one other than the recording track is selected,
|
||||
// don't bother.
|
||||
return;
|
||||
@@ -348,7 +349,7 @@ public class MyTracksMap extends MapActivity
|
||||
public void onChange(boolean selfChange) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: ContentObserver.onChange waypoints");
|
||||
if (selectedTrack == null) {
|
||||
if (!isATrackSelected()) {
|
||||
return;
|
||||
}
|
||||
updateTrackHandler.post(restoreWaypointsRunnable);
|
||||
@@ -356,37 +357,49 @@ public class MyTracksMap extends MapActivity
|
||||
}
|
||||
};
|
||||
|
||||
// Read shared preferences and register change listener:
|
||||
SharedPreferences preferences =
|
||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (preferences != null) {
|
||||
minRequiredAccuracy = preferences.getInt(
|
||||
getString(R.string.min_required_accuracy_key),
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||
recordingTrackId =
|
||||
preferences.getLong(getString(R.string.recording_track_key), -1);
|
||||
long selectedTrackId =
|
||||
preferences.getLong(getString(R.string.selected_track_key), -1);
|
||||
if (selectedTrackId >= 0) {
|
||||
setSelectedTrack(selectedTrackId);
|
||||
}
|
||||
// Read shared preferences and register change listener.
|
||||
sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (sharedPreferences != null) {
|
||||
reloadSharedPreferences(sharedPreferences, null);
|
||||
updateOptionsButton();
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupZoomControls() {
|
||||
mapView.setBuiltInZoomControls(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
// Final cleanup before activity is destroyed.
|
||||
// May not be called at all in some situations.
|
||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
||||
|
||||
if (updateTrackThread != null) {
|
||||
ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
|
||||
updateTrackThread);
|
||||
}
|
||||
if (sharedPreferences != null) {
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there's a track currently selected for display.
|
||||
*/
|
||||
private boolean isATrackSelected() {
|
||||
return selectedTrackId >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we're currently recording the same track that's selected
|
||||
* for display.
|
||||
*/
|
||||
private boolean isRecordingSelected() {
|
||||
return isATrackSelected() && selectedTrackId == recordingTrackId;
|
||||
}
|
||||
|
||||
protected void setupZoomControls() {
|
||||
mapView.setBuiltInZoomControls(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
// Called after onCreate or onStop.
|
||||
@@ -429,6 +442,9 @@ public class MyTracksMap extends MapActivity
|
||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
|
||||
super.onResume();
|
||||
|
||||
// Reload all preferences as they might have changed since last run.
|
||||
reloadSharedPreferences(sharedPreferences, null);
|
||||
|
||||
// Make sure any updates that might have happened are propagated to the
|
||||
// Map overlay:
|
||||
observer.onChange(false);
|
||||
@@ -449,10 +465,9 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
// While this activity was paused the user may have deleted the selected
|
||||
// track. In that case the map overlay needs to be cleared:
|
||||
Track track = mapOverlay.getSelectedTrack();
|
||||
if (track != null && !providerUtils.trackExists(track.getId())) {
|
||||
if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) {
|
||||
// The recording track must have been deleted meanwhile.
|
||||
mapOverlay.setSelectedTrack(null);
|
||||
mapOverlay.setTrackDrawingEnabled(false);
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
@@ -514,10 +529,10 @@ public class MyTracksMap extends MapActivity
|
||||
+ gpsProvider.getName());
|
||||
}
|
||||
locationManager.requestLocationUpdates(gpsProvider.getName(),
|
||||
0 /*minTime*/, 0 /*minDist*/, this);
|
||||
0 /*minTime*/, 0 /*minDist*/, locationListener);
|
||||
try {
|
||||
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
|
||||
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this);
|
||||
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener);
|
||||
} catch (RuntimeException e) {
|
||||
// If anything at all goes wrong with getting a cell location do not
|
||||
// abort. Cell location is not essential to this app.
|
||||
@@ -535,7 +550,7 @@ public class MyTracksMap extends MapActivity
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: Now registering sensor listeners.");
|
||||
sensorManager.registerListener(
|
||||
this, compass, SensorManager.SENSOR_DELAY_UI);
|
||||
sensorListener, compass, SensorManager.SENSOR_DELAY_UI);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,12 +560,12 @@ public class MyTracksMap extends MapActivity
|
||||
if (locationManager != null) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: Now unregistering location listeners.");
|
||||
locationManager.removeUpdates(this);
|
||||
locationManager.removeUpdates(locationListener);
|
||||
}
|
||||
if (sensorManager != null) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: Now unregistering sensor listeners.");
|
||||
sensorManager.unregisterListener(this);
|
||||
sensorManager.unregisterListener(sensorListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,7 +594,7 @@ public class MyTracksMap extends MapActivity
|
||||
*/
|
||||
private void updateOptionsButton() {
|
||||
optionsBtn.setVisibility(
|
||||
selectedTrack != null ? View.VISIBLE : View.INVISIBLE);
|
||||
isATrackSelected() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -632,12 +647,18 @@ public class MyTracksMap extends MapActivity
|
||||
/**
|
||||
* Zooms and pans the map so that the given track is visible.
|
||||
*
|
||||
* @param track a given track
|
||||
* @param trackId a given track ID
|
||||
*/
|
||||
public void showTrack(Track track) {
|
||||
if (track == null || mapView == null || track.getNumberOfPoints() < 2) {
|
||||
public void showTrack(long trackId) {
|
||||
if (mapView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Track track = providerUtils.getTrack(trackId);
|
||||
if (track == null || track.getNumberOfPoints() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
int bottom = stats.getBottom();
|
||||
int left = stats.getLeft();
|
||||
@@ -680,24 +701,33 @@ public class MyTracksMap extends MapActivity
|
||||
* @param trackId a given track id
|
||||
*/
|
||||
public void setSelectedTrack(final long trackId) {
|
||||
if (selectedTrack != null && selectedTrack.getId() == trackId) {
|
||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.setSelectedTrack: "
|
||||
+ "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId);
|
||||
|
||||
if (selectedTrackId == trackId) {
|
||||
// Selected track did not change, nothing to do.
|
||||
mapOverlay.setSelectedTrack(selectedTrack);
|
||||
mapView.invalidate();
|
||||
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
||||
updateOptionsButton();
|
||||
mapView.invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackId < 0) {
|
||||
// Remove selection.
|
||||
selectedTrack = null;
|
||||
mapOverlay.setSelectedTrack(null);
|
||||
selectedTrackId = -1;
|
||||
mapOverlay.setTrackDrawingEnabled(false);
|
||||
mapOverlay.clearWaypoints();
|
||||
updateOptionsButton();
|
||||
mapView.invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
busyPane.setVisibility(View.VISIBLE);
|
||||
selectedTrack = providerUtils.getTrack(trackId);
|
||||
selectedTrackId = trackId;
|
||||
loadSelectedTrack();
|
||||
}
|
||||
|
||||
private void loadSelectedTrack() {
|
||||
updateTrackHandler.post(restoreTrackRunnable);
|
||||
updateTrackHandler.post(restoreWaypointsRunnable);
|
||||
updateTrackHandler.post(setSelectedTrackRunnable);
|
||||
@@ -753,8 +783,7 @@ public class MyTracksMap extends MapActivity
|
||||
menu.setHeaderTitle(R.string.tracklist_this_track);
|
||||
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
|
||||
R.string.tracklist_edit_track);
|
||||
if (!MyTracks.getInstance().isRecording() || (selectedTrack != null
|
||||
&& selectedTrack.getId() != recordingTrackId)) {
|
||||
if (!isRecordingSelected()) {
|
||||
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
|
||||
R.string.tracklist_send_to_google);
|
||||
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
||||
@@ -791,7 +820,7 @@ public class MyTracksMap extends MapActivity
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
if (!super.onMenuItemSelected(featureId, item)) {
|
||||
if (selectedTrack != null) {
|
||||
if (isATrackSelected()) {
|
||||
MyTracks.getInstance().onActivityResult(
|
||||
MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK,
|
||||
new Intent());
|
||||
@@ -852,8 +881,7 @@ public class MyTracksMap extends MapActivity
|
||||
public void onClick(View v) {
|
||||
if (v == messagePane) {
|
||||
launchMyLocationSettings();
|
||||
}
|
||||
if (v == optionsBtn) {
|
||||
} else if (v == optionsBtn) {
|
||||
optionsBtn.performLongClick();
|
||||
}
|
||||
}
|
||||
@@ -874,160 +902,189 @@ public class MyTracksMap extends MapActivity
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(
|
||||
final SharedPreferences sharedPreferences,
|
||||
final String key) {
|
||||
final SharedPreferences sharedPreferences, final String key) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap.onSharedPreferenceChanged: " + key);
|
||||
if (key != null) {
|
||||
uiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (key.equals(getString(R.string.min_required_accuracy_key))) {
|
||||
minRequiredAccuracy = sharedPreferences.getInt(
|
||||
getString(R.string.min_required_accuracy_key),
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||
} else if (key.equals(getString(R.string.selected_track_key))) {
|
||||
long selectedTrackId =
|
||||
sharedPreferences.getLong(
|
||||
getString(R.string.selected_track_key),
|
||||
-1);
|
||||
setSelectedTrack(selectedTrackId);
|
||||
} else if (key.equals(getString(R.string.recording_track_key))) {
|
||||
recordingTrackId =
|
||||
sharedPreferences.getLong(
|
||||
getString(R.string.recording_track_key),
|
||||
-1);
|
||||
if (selectedTrack != null) {
|
||||
mapOverlay.setShowEndMarker(
|
||||
selectedTrack.getId() != recordingTrackId);
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
reloadSharedPreferences(sharedPreferences, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
messageText.setText(R.string.wait_for_fix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
messageText.setText(R.string.status_enable_gps);
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
messagePane.setOnClickListener(this);
|
||||
screen.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
// Recalculate the variation if there was a jump in location > 1km:
|
||||
if (currentLocation == null
|
||||
|| location.distanceTo(currentLocation) > 1000) {
|
||||
setVariation(location);
|
||||
private final LocationListener locationListener = new LocationListener() {
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
messageText.setText(R.string.wait_for_fix);
|
||||
}
|
||||
currentLocation = location;
|
||||
boolean haveGoodFixNow =
|
||||
currentLocation.getAccuracy() < minRequiredAccuracy;
|
||||
if (haveGoodFixNow != haveGoodFix) {
|
||||
haveGoodFix = haveGoodFixNow;
|
||||
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
messageText.setText(R.string.status_enable_gps);
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
messagePane.setOnClickListener(MyTracksMap.this);
|
||||
screen.requestLayout();
|
||||
}
|
||||
showCurrentLocation();
|
||||
} else {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: Network location update received.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
switch (status) {
|
||||
case LocationProvider.OUT_OF_SERVICE:
|
||||
case LocationProvider.TEMPORARILY_UNAVAILABLE:
|
||||
haveGoodFix = false;
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
// Recalculate the variation if there was a jump in location > 1km:
|
||||
if (currentLocation == null ||
|
||||
location.distanceTo(currentLocation) > 1000) {
|
||||
setVariation(location);
|
||||
}
|
||||
currentLocation = location;
|
||||
boolean haveGoodFixNow =
|
||||
currentLocation.getAccuracy() < minRequiredAccuracy;
|
||||
if (haveGoodFixNow != haveGoodFix) {
|
||||
haveGoodFix = haveGoodFixNow;
|
||||
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
|
||||
screen.requestLayout();
|
||||
break;
|
||||
}
|
||||
showCurrentLocation();
|
||||
} else {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"MyTracksMap: Network location update received.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||
switch (status) {
|
||||
case LocationProvider.OUT_OF_SERVICE:
|
||||
case LocationProvider.TEMPORARILY_UNAVAILABLE:
|
||||
haveGoodFix = false;
|
||||
messagePane.setVisibility(View.VISIBLE);
|
||||
screen.requestLayout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final SensorEventListener sensorListener = new SensorEventListener() {
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent se) {
|
||||
synchronized (this) {
|
||||
float magneticHeading = se.values[0];
|
||||
double heading = magneticHeading + variation;
|
||||
if (mapOverlay.setHeading((float) heading)) {
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor s, int accuracy) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
|
||||
private void reloadSharedPreferences(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
if (key == null ||
|
||||
key.equals(getString(R.string.min_required_accuracy_key))) {
|
||||
minRequiredAccuracy = sharedPreferences.getInt(
|
||||
getString(R.string.min_required_accuracy_key),
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||
}
|
||||
if (key == null || key.equals(getString(R.string.recording_track_key))) {
|
||||
recordingTrackId = sharedPreferences.getLong(
|
||||
getString(R.string.recording_track_key), -1);
|
||||
}
|
||||
if (key == null || key.equals(getString(R.string.selected_track_key))) {
|
||||
setSelectedTrack(sharedPreferences.getLong(
|
||||
getString(R.string.selected_track_key), -1));
|
||||
}
|
||||
|
||||
// Show end marker if the track has been selected and is not recording.
|
||||
// Note: This check must be *after* a call to setSelectedTrack(...) above.
|
||||
if (isATrackSelected()) {
|
||||
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent se) {
|
||||
synchronized (this) {
|
||||
float magneticHeading = se.values[0];
|
||||
double heading = magneticHeading + variation;
|
||||
if (mapOverlay.setHeading((float) heading)) {
|
||||
mapView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor s, int accuracy) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sampling frequency from the total number of points in the track.
|
||||
*
|
||||
* @param track The track to read the total number of points from.
|
||||
*/
|
||||
private void setSamplingFrequency(Track track) {
|
||||
long totalLocations = track.getStopId() - track.getStartId();
|
||||
|
||||
// Limit the number of map points readings.
|
||||
samplingFrequency =
|
||||
(int) (1 +
|
||||
totalLocations / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
||||
Log.i(MyTracksConstants.TAG, "Sampling locations: " + samplingFrequency);
|
||||
}
|
||||
|
||||
private void readAllNewTrackPoints() {
|
||||
Cursor cursor = null;
|
||||
// Refetch the track to get the latest StopId
|
||||
selectedTrack = providerUtils.getTrack(selectedTrack.getId());
|
||||
long totalLocations = selectedTrack.getStopId() -
|
||||
selectedTrack.getStartId();
|
||||
int numPoints = mapOverlay.getNumLocations();
|
||||
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
|
||||
// We're about to exceed the maximum allowed number of points, so reload
|
||||
// the whole track with fewer points (the sampling frequency will be
|
||||
// lower).
|
||||
loadSelectedTrack();
|
||||
return;
|
||||
}
|
||||
// Keep a copy of selectedTrackId, because it can change asynchronously.
|
||||
long currentSelectedTrackId = selectedTrackId;
|
||||
long lastStoredLocationId =
|
||||
providerUtils.getLastLocationId(currentSelectedTrackId);
|
||||
int samplingFrequency = -1;
|
||||
Location location = new Location("");
|
||||
while (currentSelectedTrackId == selectedTrackId) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = providerUtils.getLocationsCursor(currentSelectedTrackId,
|
||||
lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false);
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
// No (more) data
|
||||
break;
|
||||
}
|
||||
|
||||
setSamplingFrequency(selectedTrack);
|
||||
int bufferSize = 1024;
|
||||
int points = 0;
|
||||
while (lastSeenLocationId < selectedTrack.getStopId()) {
|
||||
cursor = providerUtils.getLocationsCursor(
|
||||
selectedTrack.getId(), lastSeenLocationId, bufferSize, false);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
final int idColumnIdx = cursor.getColumnIndexOrThrow(
|
||||
TrackPointsColumns._ID);
|
||||
while (cursor.moveToNext()) {
|
||||
points++;
|
||||
Location location = providerUtils.createLocation(cursor);
|
||||
lastSeenLocationId = cursor.getLong(idColumnIdx);
|
||||
do {
|
||||
long locationId = cursor.getLong(idColumnIdx);
|
||||
lastSeenLocationId = locationId;
|
||||
if (firstSeenLocationId == -1) {
|
||||
// This was our first point, keep its ID
|
||||
firstSeenLocationId = locationId;
|
||||
}
|
||||
if (samplingFrequency == -1) {
|
||||
// Now we already have at least one point, calculate the sampling
|
||||
// frequency
|
||||
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
||||
samplingFrequency = (int) (1 + numTotalPoints
|
||||
/ MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
||||
// TODO: This shouldn't happen after adding currentSelectedTrackId,
|
||||
// but just to be safe until we have 100% confidence.
|
||||
if (samplingFrequency <= 0) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"readAllNewTrackPoints: samplingFreq <= 0, numTotalPoints = "
|
||||
+ numTotalPoints + ", trackId = " + currentSelectedTrackId);
|
||||
samplingFrequency = 1;
|
||||
}
|
||||
}
|
||||
|
||||
providerUtils.fillLocation(cursor, location);
|
||||
|
||||
// Include a point if it fits one of the following criteria:
|
||||
// - Has the mod for the sampling frequency.
|
||||
// - Is the first point.
|
||||
// - Has the mod for the sampling frequency (includes first point).
|
||||
// - Is the last point and we are not recording this track.
|
||||
if (!MyTracksUtils.isValidLocation(location) ||
|
||||
points % samplingFrequency == 0 ||
|
||||
points == 0 ||
|
||||
(recordingTrackId != selectedTrack.getId() &&
|
||||
points == (totalLocations - 1))) {
|
||||
// - The point is a segment split
|
||||
if (numPoints % samplingFrequency == 0 ||
|
||||
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
||||
!MyTracksUtils.isValidLocation(location)) {
|
||||
// No need to allocate a new location (we can safely reuse the existing).
|
||||
mapOverlay.addLocation(location);
|
||||
}
|
||||
|
||||
numPoints++;
|
||||
} while (cursor.moveToNext() &&
|
||||
currentSelectedTrackId == selectedTrackId);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
} else {
|
||||
lastSeenLocationId += bufferSize;
|
||||
}
|
||||
cursor.close();
|
||||
cursor = null;
|
||||
}
|
||||
mapView.postInvalidate();
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
|
||||
import com.google.android.maps.GeoPoint;
|
||||
import com.google.android.maps.MapView;
|
||||
import com.google.android.maps.Overlay;
|
||||
import com.google.android.maps.Projection;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
@@ -35,6 +35,9 @@ import android.graphics.drawable.Drawable;
|
||||
import android.location.Location;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
* A map overlay that displays a "MyLocation" arrow, an error circle, the
|
||||
@@ -44,7 +47,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class MyTracksOverlay extends Overlay {
|
||||
|
||||
private final Drawable arrow[] = new Drawable[18];
|
||||
private final Drawable[] arrows;
|
||||
private final int arrowWidth, arrowHeight;
|
||||
private final Drawable statsMarker;
|
||||
private final Drawable waypointMarker;
|
||||
@@ -54,102 +57,152 @@ public class MyTracksOverlay extends Overlay {
|
||||
private final Paint selectedTrackPaint;
|
||||
private final Paint errorCirclePaint;
|
||||
private final Context context;
|
||||
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||
private final ArrayList<Location> points = new ArrayList<Location>(1024);
|
||||
private final List<Waypoint> waypoints;
|
||||
private final List<CachedLocation> points;
|
||||
private final BlockingQueue<CachedLocation> pendingPoints;
|
||||
|
||||
private Track selectedTrack;
|
||||
private boolean trackDrawingEnabled;
|
||||
private int lastHeading = 0;
|
||||
private Location myLocation;
|
||||
private boolean showEndMarker = true;
|
||||
// TODO: Remove it completely after completing performance tests.
|
||||
private boolean alwaysVisible = true;
|
||||
|
||||
private GeoPoint lastReferencePoint;
|
||||
private Rect lastViewRect;
|
||||
private Path lastPath;
|
||||
|
||||
/**
|
||||
* 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 final boolean valid;
|
||||
public final GeoPoint geoPoint;
|
||||
|
||||
public CachedLocation(Location location) {
|
||||
this.valid = MyTracksUtils.isValidLocation(location);
|
||||
this.geoPoint = valid ? MyTracksUtils.getGeoPoint(location) : null;
|
||||
}
|
||||
};
|
||||
|
||||
public MyTracksOverlay(Context context) {
|
||||
this.context = context;
|
||||
|
||||
this.waypoints = new ArrayList<Waypoint>();
|
||||
this.points = new ArrayList<CachedLocation>(1024);
|
||||
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
||||
MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS, true);
|
||||
|
||||
// TODO: Can we use a FrameAnimation or similar here rather
|
||||
// than individual resources for each arrow direction?
|
||||
|
||||
arrow[0] = context.getResources().getDrawable(R.drawable.arrow_0);
|
||||
arrow[1] = context.getResources().getDrawable(R.drawable.arrow_20);
|
||||
arrow[2] = context.getResources().getDrawable(R.drawable.arrow_40);
|
||||
arrow[3] = context.getResources().getDrawable(R.drawable.arrow_60);
|
||||
arrow[4] = context.getResources().getDrawable(R.drawable.arrow_80);
|
||||
arrow[5] = context.getResources().getDrawable(R.drawable.arrow_100);
|
||||
arrow[6] = context.getResources().getDrawable(R.drawable.arrow_120);
|
||||
arrow[7] = context.getResources().getDrawable(R.drawable.arrow_140);
|
||||
arrow[8] = context.getResources().getDrawable(R.drawable.arrow_160);
|
||||
arrow[9] = context.getResources().getDrawable(R.drawable.arrow_180);
|
||||
arrow[10] = context.getResources().getDrawable(R.drawable.arrow_200);
|
||||
arrow[11] = context.getResources().getDrawable(R.drawable.arrow_220);
|
||||
arrow[12] = context.getResources().getDrawable(R.drawable.arrow_240);
|
||||
arrow[13] = context.getResources().getDrawable(R.drawable.arrow_260);
|
||||
arrow[14] = context.getResources().getDrawable(R.drawable.arrow_280);
|
||||
arrow[15] = context.getResources().getDrawable(R.drawable.arrow_300);
|
||||
arrow[16] = context.getResources().getDrawable(R.drawable.arrow_320);
|
||||
arrow[17] = context.getResources().getDrawable(R.drawable.arrow_340);
|
||||
arrowWidth = arrow[lastHeading].getIntrinsicWidth();
|
||||
arrowHeight = arrow[lastHeading].getIntrinsicHeight();
|
||||
for (int i = 0; i <= 17; i++) {
|
||||
arrow[i].setBounds(0, 0, arrowWidth, arrowHeight);
|
||||
final Resources resources = context.getResources();
|
||||
arrows = new Drawable[] {
|
||||
resources.getDrawable(R.drawable.arrow_0),
|
||||
resources.getDrawable(R.drawable.arrow_20),
|
||||
resources.getDrawable(R.drawable.arrow_40),
|
||||
resources.getDrawable(R.drawable.arrow_60),
|
||||
resources.getDrawable(R.drawable.arrow_80),
|
||||
resources.getDrawable(R.drawable.arrow_100),
|
||||
resources.getDrawable(R.drawable.arrow_120),
|
||||
resources.getDrawable(R.drawable.arrow_140),
|
||||
resources.getDrawable(R.drawable.arrow_160),
|
||||
resources.getDrawable(R.drawable.arrow_180),
|
||||
resources.getDrawable(R.drawable.arrow_200),
|
||||
resources.getDrawable(R.drawable.arrow_220),
|
||||
resources.getDrawable(R.drawable.arrow_240),
|
||||
resources.getDrawable(R.drawable.arrow_260),
|
||||
resources.getDrawable(R.drawable.arrow_280),
|
||||
resources.getDrawable(R.drawable.arrow_300),
|
||||
resources.getDrawable(R.drawable.arrow_320),
|
||||
resources.getDrawable(R.drawable.arrow_340)
|
||||
};
|
||||
arrowWidth = arrows[lastHeading].getIntrinsicWidth();
|
||||
arrowHeight = arrows[lastHeading].getIntrinsicHeight();
|
||||
for (Drawable arrow : arrows) {
|
||||
arrow.setBounds(0, 0, arrowWidth, arrowHeight);
|
||||
}
|
||||
|
||||
statsMarker = context.getResources().getDrawable(R.drawable.ylw_pushpin);
|
||||
statsMarker = resources.getDrawable(R.drawable.ylw_pushpin);
|
||||
markerWidth = statsMarker.getIntrinsicWidth();
|
||||
markerHeight = statsMarker.getIntrinsicHeight();
|
||||
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
startMarker = context.getResources().getDrawable(R.drawable.green_dot);
|
||||
startMarker = resources.getDrawable(R.drawable.green_dot);
|
||||
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
endMarker = context.getResources().getDrawable(R.drawable.red_dot);
|
||||
endMarker = resources.getDrawable(R.drawable.red_dot);
|
||||
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
waypointMarker =
|
||||
context.getResources().getDrawable(R.drawable.blue_pushpin);
|
||||
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
|
||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||
|
||||
selectedTrackPaint = new Paint();
|
||||
selectedTrackPaint.setColor(context.getResources().getColor(R.color.red));
|
||||
selectedTrackPaint.setColor(resources.getColor(R.color.red));
|
||||
selectedTrackPaint.setStrokeWidth(3);
|
||||
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
||||
selectedTrackPaint.setAntiAlias(true);
|
||||
|
||||
errorCirclePaint = new Paint();
|
||||
errorCirclePaint.setColor(context.getResources().getColor(R.color.blue));
|
||||
errorCirclePaint.setColor(resources.getColor(R.color.blue));
|
||||
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
||||
errorCirclePaint.setStrokeWidth(3);
|
||||
errorCirclePaint.setAlpha(127);
|
||||
errorCirclePaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public void setSelectedTrack(Track track) {
|
||||
selectedTrack = track;
|
||||
}
|
||||
|
||||
public Track getSelectedTrack() {
|
||||
return selectedTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a location to the map overlay.
|
||||
*
|
||||
* NOTE: This method doesn't take ownership of the given location, so it is
|
||||
* safe to reuse the same location while calling this method.
|
||||
*
|
||||
* NOTE: this method takes ownership of this location and may change it.
|
||||
*
|
||||
* @param l the location to add
|
||||
* @param l the location to add.
|
||||
*/
|
||||
public void addLocation(Location l) {
|
||||
points.add(l);
|
||||
// Queue up in the pending queue until it's merged with {@code #points}.
|
||||
pendingPoints.offer(new CachedLocation(l));
|
||||
}
|
||||
|
||||
public void addWaypoint(Waypoint wpt) {
|
||||
waypoints.add(wpt);
|
||||
// Note: We don't cache waypoints, because it's not worth the effort.
|
||||
if (wpt != null && wpt.getLocation() != null) {
|
||||
synchronized (waypoints) {
|
||||
waypoints.add(wpt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumLocations() {
|
||||
synchronized (points) {
|
||||
return points.size() + pendingPoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
int getNumWaypoints() {
|
||||
synchronized (waypoints) {
|
||||
return waypoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearPoints() {
|
||||
synchronized (points) {
|
||||
points.clear();
|
||||
pendingPoints.clear();
|
||||
lastPath = null;
|
||||
lastViewRect = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearWaypoints() {
|
||||
waypoints.clear();
|
||||
synchronized (waypoints) {
|
||||
waypoints.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearPoints() {
|
||||
points.clear();
|
||||
public void setTrackDrawingEnabled(boolean trackDrawingEnabled) {
|
||||
this.trackDrawingEnabled = trackDrawingEnabled;
|
||||
}
|
||||
|
||||
public void setShowEndMarker(boolean showEndMarker) {
|
||||
@@ -158,159 +211,179 @@ public class MyTracksOverlay extends Overlay {
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
||||
|
||||
if (shadow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw the selected track:
|
||||
drawTrack(canvas, mapView, selectedTrack, true);
|
||||
// It's safe to keep projection within a single draw operation.
|
||||
final Projection projection = getMapProjection(mapView);
|
||||
// Get the current viewing window.
|
||||
if (trackDrawingEnabled) {
|
||||
Rect viewRect = getMapViewRect(mapView);
|
||||
|
||||
// Draw the selected track:
|
||||
drawTrack(canvas, projection, viewRect);
|
||||
|
||||
// Draw the waypoints:
|
||||
ArrayList<Waypoint> currentWaypoints = waypoints;
|
||||
for (int i = 1; i < currentWaypoints.size(); i++) {
|
||||
Waypoint wpt = currentWaypoints.get(i);
|
||||
if (wpt == null) {
|
||||
continue;
|
||||
}
|
||||
Location loc = wpt.getLocation();
|
||||
if (loc == null) {
|
||||
continue;
|
||||
}
|
||||
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(loc);
|
||||
Point pt = new Point();
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
canvas.save();
|
||||
canvas.translate(pt.x - (markerWidth / 2) + 3, pt.y - (markerHeight));
|
||||
if (wpt.getType() == Waypoint.TYPE_STATISTICS) {
|
||||
statsMarker.draw(canvas);
|
||||
} else {
|
||||
waypointMarker.draw(canvas);
|
||||
}
|
||||
canvas.restore();
|
||||
// Draw the waypoints:
|
||||
drawWaypoints(canvas, projection);
|
||||
}
|
||||
|
||||
// Draw the arrow icon:
|
||||
if (myLocation == null) {
|
||||
return;
|
||||
}
|
||||
GeoPoint geoPoint = new GeoPoint(
|
||||
(int) (myLocation.getLatitude() * 1E6),
|
||||
(int) (myLocation.getLongitude() * 1E6));
|
||||
Point pt = new Point();
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
canvas.save();
|
||||
canvas.translate(pt.x - (arrowWidth / 2), pt.y - (arrowHeight / 2));
|
||||
arrow[lastHeading].draw(canvas);
|
||||
canvas.restore();
|
||||
|
||||
// Draw the error circle:
|
||||
float radius =
|
||||
mapView.getProjection().metersToEquatorPixels(myLocation.getAccuracy());
|
||||
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
||||
// Draw the current location
|
||||
drawMyLocation(canvas, projection);
|
||||
}
|
||||
|
||||
public void drawTrack(Canvas canvas, MapView mapView, Track track,
|
||||
boolean selected) {
|
||||
|
||||
if (track == null) {
|
||||
return;
|
||||
}
|
||||
if (points.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current viewing window:
|
||||
|
||||
// Visible for testing.
|
||||
Projection getMapProjection(MapView mapView) {
|
||||
return mapView.getProjection();
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
Rect getMapViewRect(MapView mapView) {
|
||||
int w = mapView.getLongitudeSpan();
|
||||
int h = mapView.getLatitudeSpan();
|
||||
int cx = mapView.getMapCenter().getLongitudeE6();
|
||||
int cy = mapView.getMapCenter().getLatitudeE6();
|
||||
Rect rect = new Rect(cx - w, cy - h, cx + w, cy + h);
|
||||
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
Path newPath() {
|
||||
return new Path();
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
Path getLastPath() {
|
||||
return lastPath;
|
||||
}
|
||||
|
||||
Point pt = new Point();
|
||||
GeoPoint geoPoint;
|
||||
Location loc;
|
||||
Location lastValidLocation = null;
|
||||
Path path;
|
||||
boolean wasInside;
|
||||
|
||||
// Do as much allocation and preparation outside the loop over track
|
||||
// points:
|
||||
wasInside = false;
|
||||
int locLon = 0, locLat = 0, minLon, maxLon, minLat, maxLat;
|
||||
int lastLocLon = (int) (points.get(0).getLongitude() * 1E6);
|
||||
int lastLocLat = (int) (points.get(0).getLatitude() * 1E6);
|
||||
|
||||
// Loop over track points:
|
||||
path = new Path();
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
|
||||
loc = points.get(i);
|
||||
if (loc == null) {
|
||||
continue;
|
||||
}
|
||||
locLon = (int) (loc.getLongitude() * 1E6);
|
||||
locLat = (int) (loc.getLatitude() * 1E6);
|
||||
|
||||
// Draw a line segment if it's inside the viewing window:
|
||||
if (locLat < 90E6 && lastLocLat < 90E6) {
|
||||
lastValidLocation = loc;
|
||||
minLon = Math.min(locLon, lastLocLon);
|
||||
maxLon = Math.max(locLon, lastLocLon);
|
||||
minLat = Math.min(locLat, lastLocLat);
|
||||
maxLat = Math.max(locLat, lastLocLat);
|
||||
if (rect.intersects(minLon, minLat, maxLon, maxLat)) {
|
||||
if (!wasInside) {
|
||||
geoPoint = new GeoPoint(lastLocLat, lastLocLon);
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
path.moveTo(pt.x, pt.y);
|
||||
}
|
||||
geoPoint = new GeoPoint(locLat, locLon);
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
path.lineTo(pt.x, pt.y);
|
||||
wasInside = rect.contains(locLon, locLat);
|
||||
}
|
||||
} else {
|
||||
wasInside = false;
|
||||
}
|
||||
lastLocLon = locLon;
|
||||
lastLocLat = locLat;
|
||||
}
|
||||
canvas.drawPath(path, selectedTrackPaint);
|
||||
|
||||
// Draw the "End" marker:
|
||||
Location currentLastValidLocation = lastValidLocation;
|
||||
if (showEndMarker && currentLastValidLocation != null) {
|
||||
canvas.save();
|
||||
geoPoint = MyTracksUtils.getGeoPoint(currentLastValidLocation);
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
||||
endMarker.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
// Draw the "Start" marker:
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
loc = points.get(i);
|
||||
if (loc.getLatitude() < 90) {
|
||||
locLon = (int) (loc.getLongitude() * 1E6);
|
||||
locLat = (int) (loc.getLatitude() * 1E6);
|
||||
geoPoint = new GeoPoint(locLat, locLon);
|
||||
mapView.getProjection().toPixels(geoPoint, pt);
|
||||
canvas.save();
|
||||
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
||||
startMarker.draw(canvas);
|
||||
canvas.restore();
|
||||
break;
|
||||
private void drawWaypoints(Canvas canvas, Projection projection) {
|
||||
synchronized (waypoints) {;
|
||||
for (Waypoint wpt : waypoints) {
|
||||
Location loc = wpt.getLocation();
|
||||
drawElement(canvas, projection, MyTracksUtils.getGeoPoint(loc),
|
||||
wpt.getType() == Waypoint.TYPE_STATISTICS ? statsMarker
|
||||
: waypointMarker, -(markerWidth / 2) + 3, -markerHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
|
||||
long when) {
|
||||
draw(canvas, mapView, shadow);
|
||||
return false;
|
||||
private void drawMyLocation(Canvas canvas, Projection projection) {
|
||||
// Draw the arrow icon.
|
||||
if (myLocation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Point pt = drawElement(canvas, projection,
|
||||
MyTracksUtils.getGeoPoint(myLocation), arrows[lastHeading],
|
||||
-(arrowWidth / 2) + 3, -(arrowHeight / 2));
|
||||
// Draw the error circle.
|
||||
float radius = projection.metersToEquatorPixels(myLocation.getAccuracy());
|
||||
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
||||
}
|
||||
|
||||
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
|
||||
Path path;
|
||||
synchronized (points) {
|
||||
// Merge the pending points with the list of cached locations.
|
||||
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
||||
int newPoints = pendingPoints.drainTo(points);
|
||||
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
||||
!referencePoint.equals(lastReferencePoint);
|
||||
if (newPoints == 0 && lastPath != null && !newProjection) {
|
||||
// No need to recreate path (same points and viewing area).
|
||||
path = lastPath;
|
||||
} else {
|
||||
int numPoints = points.size();
|
||||
if (numPoints < 2) {
|
||||
// Not enough points to draw a path.
|
||||
path = null;
|
||||
} else if (lastPath != null && !newProjection) {
|
||||
// Incremental update of the path, without repositioning the view.
|
||||
path = lastPath;
|
||||
updatePath(projection, viewRect, path, numPoints - newPoints);
|
||||
} else {
|
||||
// The view has changed so we have to start from scratch.
|
||||
path = newPath();
|
||||
path.incReserve(numPoints);
|
||||
updatePath(projection, viewRect, path, 0);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
||||
Drawable element, int offsetX, int offsetY) {
|
||||
Point pt = new Point();
|
||||
projection.toPixels(geoPoint, pt);
|
||||
canvas.save();
|
||||
canvas.translate(pt.x + offsetX, pt.y + offsetY);
|
||||
element.draw(canvas);
|
||||
canvas.restore();
|
||||
return pt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,20 +426,22 @@ public class MyTracksOverlay extends Overlay {
|
||||
final Location tapLocation = MyTracksUtils.getLocation(p);
|
||||
double dmin = Double.MAX_VALUE;
|
||||
Waypoint waypoint = null;
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||
if (waypointLocation == null) {
|
||||
continue;
|
||||
}
|
||||
final double d = waypointLocation.distanceTo(tapLocation);
|
||||
if (d < dmin) {
|
||||
dmin = d;
|
||||
waypoint = waypoints.get(i);
|
||||
synchronized (waypoints) {
|
||||
for (int i = 0; i < waypoints.size(); i++) {
|
||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||
if (waypointLocation == null) {
|
||||
continue;
|
||||
}
|
||||
final double d = waypointLocation.distanceTo(tapLocation);
|
||||
if (d < dmin) {
|
||||
dmin = d;
|
||||
waypoint = waypoints.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (waypoint != null
|
||||
&& dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||
if (waypoint != null &&
|
||||
dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
||||
intent.putExtra("waypointid", waypoint.getId());
|
||||
context.startActivity(intent);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import static android.content.Intent.ACTION_BOOT_COMPLETED;
|
||||
import static com.google.android.apps.mytracks.MyTracksConstants.RESUME_TRACK_EXTRA_NAME;
|
||||
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
|
||||
|
||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class handles MyTracks related broadcast messages.
|
||||
*
|
||||
* One example of a broadcast message that this class is interested in,
|
||||
* is notification about the phone boot. We may want to resume a previously
|
||||
* started tracking session if the phone crashed (hopefully not), or the user
|
||||
* decided to swap the battery or some external event occurred which forced
|
||||
* a phone reboot.
|
||||
*
|
||||
* This class simply delegates to {@link TrackRecordingService} to make a
|
||||
* decision whether to continue with the previous track (if any), or just
|
||||
* abandon it.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class MyTracksReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "MyTracksReceiver.onReceive: " + intent.getAction());
|
||||
if (ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Intent startIntent = new Intent(context, TrackRecordingService.class);
|
||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
||||
context.startService(startIntent);
|
||||
} else {
|
||||
Log.w(TAG, "MyTracksReceiver: unsupported action");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@ package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
@@ -25,12 +26,10 @@ import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
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.PreferenceManager;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.Log;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
|
||||
/**
|
||||
* An activity that let's the user see and edit the settings.
|
||||
@@ -45,6 +44,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
/*
|
||||
* Default values - keep in sync with those in preferences.xml.
|
||||
*/
|
||||
public static final int DEFAULT_AUTO_RESUME_TRACK_TIMEOUT = 10; // In min.
|
||||
public static final int DEFAULT_ANNOUNCEMENT_FREQUENCY = -1;
|
||||
public static final int DEFAULT_MAX_RECORDING_DISTANCE = 200;
|
||||
public static final int DEFAULT_MIN_RECORDING_DISTANCE = 5;
|
||||
@@ -52,29 +52,19 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
|
||||
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
||||
|
||||
private static boolean mTTSAvailable;
|
||||
private BackupPreferencesListener backupListener;
|
||||
|
||||
private SharedPreferences preferences;
|
||||
|
||||
/* establish whether the tts class is available to us */
|
||||
static {
|
||||
try {
|
||||
SafeStatusAnnouncerTask.checkAvailable();
|
||||
mTTSAvailable = true;
|
||||
} catch (Throwable t) {
|
||||
Log.d(MyTracksConstants.TAG, "TTS not available.", t);
|
||||
mTTSAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
|
||||
// Tell it where to read/write preferences
|
||||
PreferenceManager preferenceManager = getPreferenceManager();
|
||||
@@ -82,7 +72,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
preferenceManager.setSharedPreferencesMode(0);
|
||||
|
||||
// Set up automatic preferences backup
|
||||
backupListener = BackupPreferencesListener.create(this);
|
||||
backupListener = BackupPreferencesListener.create(this, apiFeatures);
|
||||
preferences = preferenceManager.getSharedPreferences();
|
||||
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
||||
|
||||
@@ -107,14 +97,14 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
||||
|
||||
// Disable TTS announcement preference if not available
|
||||
if (!mTTSAvailable) {
|
||||
if (!apiFeatures.hasTextToSpeech()) {
|
||||
IntegerListPreference announcementFrequency =
|
||||
(IntegerListPreference) findPreference(
|
||||
getString(R.string.announcement_frequency_key));
|
||||
announcementFrequency.setEnabled(false);
|
||||
announcementFrequency.setValue("-1");
|
||||
announcementFrequency.setSummary(
|
||||
R.string.settings_announcement_not_available_summary);
|
||||
R.string.settings_not_available_summary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +158,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(backupListener);
|
||||
|
||||
super.onPause();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +180,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
||||
final ListPreference splitFrequency =
|
||||
(ListPreference) findPreference(
|
||||
getString(R.string.split_frequency_key));
|
||||
|
||||
|
||||
minRecordingDistance.setEntries(isMetric
|
||||
? R.array.min_recording_distance_options
|
||||
: R.array.min_recording_distance_options_ft);
|
||||
|
||||
@@ -135,7 +135,6 @@ public class MyTracksWaypointDetails extends Activity
|
||||
iconId = R.drawable.ylw_pushpin;
|
||||
TripStatistics waypointStats = waypoint.getStatistics();
|
||||
utils.setAllStats(waypointStats);
|
||||
utils.setTime(R.id.total_time_register, waypointStats.getTotalTime());
|
||||
utils.setAltitude(
|
||||
R.id.elevation_register, waypoint.getLocation().getAltitude());
|
||||
break;
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
@@ -50,21 +49,18 @@ public class NavControls {
|
||||
|
||||
/**
|
||||
* A touchable image view.
|
||||
* When touched it changes the navigation control icons accordingly.
|
||||
*/
|
||||
public class TouchLayout extends RelativeLayout {
|
||||
private final ImageView arrow;
|
||||
private class TouchLayout extends RelativeLayout implements Runnable {
|
||||
private final boolean isLeft;
|
||||
private final ImageView icon;
|
||||
|
||||
public TouchLayout(Context context, boolean isLeft) {
|
||||
super(context);
|
||||
arrow = new ImageView(context);
|
||||
arrow.setImageDrawable(context.getResources().getDrawable(
|
||||
isLeft ? R.drawable.btn_arrow_left : R.drawable.btn_arrow_right));
|
||||
icon = new ImageView(context);
|
||||
this.isLeft = isLeft;
|
||||
this.icon = new ImageView(context);
|
||||
icon.setVisibility(View.GONE);
|
||||
addView(arrow);
|
||||
addView(icon);
|
||||
icon.setPadding((isLeft ? 15 : 10), 27, 15, 0);
|
||||
}
|
||||
|
||||
public void setIcon(Drawable drawable) {
|
||||
@@ -77,18 +73,11 @@ public class NavControls {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setPressed(true);
|
||||
hide();
|
||||
if (this == prevImage) {
|
||||
if (prevRunnable != null) {
|
||||
handler.post(prevRunnable);
|
||||
return true;
|
||||
}
|
||||
} else if (this == nextImage) {
|
||||
if (nextRunnable != null) {
|
||||
handler.post(nextRunnable);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
shiftIcons(isLeft);
|
||||
|
||||
// Call the user back
|
||||
handler.post(this);
|
||||
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
setPressed(false);
|
||||
@@ -96,55 +85,69 @@ public class NavControls {
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
touchRunnable.run();
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final Runnable prevRunnable;
|
||||
private final Runnable nextRunnable;
|
||||
private final Handler handler = new Handler();
|
||||
private final Runnable dismissControls = new Runnable() {
|
||||
public void run() {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
|
||||
private final TouchLayout prevImage;
|
||||
private final TouchLayout nextImage;
|
||||
private final TypedArray leftIcons;
|
||||
private final TypedArray rightIcons;
|
||||
private final Runnable touchRunnable;
|
||||
|
||||
private boolean isVisible = false;
|
||||
private boolean hasNext = true;
|
||||
private boolean hasPrev = true;
|
||||
private int currentIcons;
|
||||
|
||||
public NavControls(Context context, ViewGroup container,
|
||||
Runnable prevRunnable, Runnable nextRunnable) {
|
||||
this.context = context;
|
||||
this.prevRunnable = prevRunnable;
|
||||
this.nextRunnable = nextRunnable;
|
||||
TypedArray leftIcons, TypedArray rightIcons,
|
||||
Runnable touchRunnable) {
|
||||
this.leftIcons = leftIcons;
|
||||
this.rightIcons = rightIcons;
|
||||
this.touchRunnable = touchRunnable;
|
||||
|
||||
if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
|
||||
throw new IllegalArgumentException("Invalid icons specified");
|
||||
}
|
||||
if (touchRunnable == null) {
|
||||
throw new NullPointerException("Runnable cannot be null");
|
||||
}
|
||||
|
||||
LayoutParams prevParams = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
||||
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
prevImage = new TouchLayout(context, true);
|
||||
prevImage.setLayoutParams(prevParams);
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
container.addView(prevImage);
|
||||
LayoutParams nextParams = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
|
||||
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
||||
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||
|
||||
nextImage = new TouchLayout(context, false);
|
||||
prevImage = new TouchLayout(context, true);
|
||||
nextImage.setLayoutParams(nextParams);
|
||||
prevImage.setLayoutParams(prevParams);
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
|
||||
container.addView(prevImage);
|
||||
container.addView(nextImage);
|
||||
}
|
||||
|
||||
public void setHasNext(boolean hasNext) {
|
||||
this.hasNext = hasNext;
|
||||
}
|
||||
|
||||
public void setHasPrev(boolean hasPrev) {
|
||||
this.hasPrev = hasPrev;
|
||||
prevImage.setIcon(leftIcons.getDrawable(0));
|
||||
nextImage.setIcon(rightIcons.getDrawable(0));
|
||||
this.currentIcons = 0;
|
||||
}
|
||||
|
||||
private void keepVisible() {
|
||||
@@ -156,20 +159,18 @@ public class NavControls {
|
||||
|
||||
public void show() {
|
||||
if (!isVisible) {
|
||||
if (prevRunnable != null && hasPrev) {
|
||||
SHOW_PREV_ANIMATION.setDuration(500);
|
||||
SHOW_PREV_ANIMATION.startNow();
|
||||
prevImage.setPressed(false);
|
||||
prevImage.setAnimation(SHOW_PREV_ANIMATION);
|
||||
prevImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (nextRunnable != null && hasNext) {
|
||||
SHOW_NEXT_ANIMATION.setDuration(500);
|
||||
SHOW_NEXT_ANIMATION.startNow();
|
||||
nextImage.setPressed(false);
|
||||
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
|
||||
nextImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
SHOW_PREV_ANIMATION.setDuration(500);
|
||||
SHOW_PREV_ANIMATION.startNow();
|
||||
prevImage.setPressed(false);
|
||||
prevImage.setAnimation(SHOW_PREV_ANIMATION);
|
||||
prevImage.setVisibility(View.VISIBLE);
|
||||
|
||||
SHOW_NEXT_ANIMATION.setDuration(500);
|
||||
SHOW_NEXT_ANIMATION.startNow();
|
||||
nextImage.setPressed(false);
|
||||
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
|
||||
nextImage.setVisibility(View.VISIBLE);
|
||||
|
||||
isVisible = true;
|
||||
keepVisible();
|
||||
} else {
|
||||
@@ -188,33 +189,26 @@ public class NavControls {
|
||||
|
||||
public void hide() {
|
||||
isVisible = false;
|
||||
if (prevRunnable != null) {
|
||||
if (hasPrev) {
|
||||
prevImage.setAnimation(HIDE_PREV_ANIMATION);
|
||||
HIDE_PREV_ANIMATION.setDuration(500);
|
||||
HIDE_PREV_ANIMATION.startNow();
|
||||
} else {
|
||||
prevImage.clearAnimation();
|
||||
}
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (nextRunnable != null) {
|
||||
if (hasNext) {
|
||||
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
|
||||
HIDE_NEXT_ANIMATION.setDuration(500);
|
||||
HIDE_NEXT_ANIMATION.startNow();
|
||||
} else {
|
||||
nextImage.clearAnimation();
|
||||
}
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
prevImage.setAnimation(HIDE_PREV_ANIMATION);
|
||||
HIDE_PREV_ANIMATION.setDuration(500);
|
||||
HIDE_PREV_ANIMATION.startNow();
|
||||
prevImage.setVisibility(View.INVISIBLE);
|
||||
|
||||
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
|
||||
HIDE_NEXT_ANIMATION.setDuration(500);
|
||||
HIDE_NEXT_ANIMATION.startNow();
|
||||
nextImage.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void setLeftIcon(int resourceId) {
|
||||
prevImage.setIcon(context.getResources().getDrawable(resourceId));
|
||||
public int getCurrentIcons() {
|
||||
return currentIcons;
|
||||
}
|
||||
|
||||
public void setRightIcon(int resourceId) {
|
||||
nextImage.setIcon(context.getResources().getDrawable(resourceId));
|
||||
private void shiftIcons(boolean isLeft) {
|
||||
// Increment or decrement by one, with wrap around
|
||||
currentIcons = (currentIcons + leftIcons.length() +
|
||||
(isLeft ? -1 : 1)) % leftIcons.length();
|
||||
prevImage.setIcon(leftIcons.getDrawable(currentIcons));
|
||||
nextImage.setIcon(rightIcons.getDrawable(currentIcons));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ package com.google.android.apps.mytracks;
|
||||
*/
|
||||
public interface ProgressIndicator {
|
||||
|
||||
public void setProgressMessage(String text);
|
||||
public void setProgressMessage(int resId);
|
||||
public void clearProgressMessage();
|
||||
|
||||
public void setProgressValue(int percent);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SendToGoogleDialog extends Dialog {
|
||||
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
|
||||
cancel.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
MyTracks.getInstance().dismissDialog(MyTracks.DIALOG_SEND_TO_GOOGLE);
|
||||
MyTracks.getInstance().dismissDialog(DialogManager.DIALOG_SEND_TO_GOOGLE);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.app.Activity;
|
||||
@@ -33,7 +35,6 @@ import android.location.LocationManager;
|
||||
import android.location.LocationProvider;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
@@ -49,14 +50,12 @@ import android.widget.Toast;
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class StatsActivity extends Activity
|
||||
implements OnSharedPreferenceChangeListener, LocationListener {
|
||||
implements OnSharedPreferenceChangeListener {
|
||||
|
||||
|
||||
private final StatsUtilities utils;
|
||||
private StatsUtilities utils;
|
||||
private UIUpdateThread thread;
|
||||
|
||||
private ContentObserver observer;
|
||||
private LocationManager locationManager;
|
||||
|
||||
/**
|
||||
* The id of the currently selected track.
|
||||
@@ -95,8 +94,7 @@ public class StatsActivity extends Activity
|
||||
*/
|
||||
private boolean showCurrentSegment = false;
|
||||
|
||||
private final MyTracksProviderUtils providerUtils;
|
||||
private Track track = null;
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
|
||||
/**
|
||||
* A runnable for posting to the UI thread. Will update the total time field.
|
||||
@@ -135,18 +133,18 @@ public class StatsActivity extends Activity
|
||||
}
|
||||
}
|
||||
|
||||
public StatsActivity() {
|
||||
utils = new StatsUtilities(this);
|
||||
providerUtils = new MyTracksProviderUtilsImpl(this);
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
utils = new StatsUtilities(this);
|
||||
providerUtils = new MyTracksProviderUtilsImpl(getContentResolver());
|
||||
|
||||
// The volume we want to control is the Text-To-Speech volume
|
||||
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||
int volumeStream =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||
setVolumeControlStream(volumeStream);
|
||||
|
||||
// We don't need a window title bar:
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
@@ -179,11 +177,9 @@ public class StatsActivity extends Activity
|
||||
preferences.getBoolean(getString(R.string.report_speed_key), true);
|
||||
checkLiveTrack();
|
||||
restoreStats();
|
||||
updateLocation(null);
|
||||
showUnknownLocation();
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
utils.setMetricUnits(metricUnits);
|
||||
utils.setReportSpeed(displaySpeed);
|
||||
utils.updateUnits();
|
||||
@@ -214,7 +210,7 @@ public class StatsActivity extends Activity
|
||||
activityOnTop = true;
|
||||
checkLiveTrack();
|
||||
restoreStats();
|
||||
updateLocation(null);
|
||||
showUnknownLocation();
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@@ -234,7 +230,7 @@ public class StatsActivity extends Activity
|
||||
-1);
|
||||
checkLiveTrack();
|
||||
restoreStats();
|
||||
updateLocation(null);
|
||||
showUnknownLocation();
|
||||
} else if (key.equals(getString(R.string.recording_track_key))) {
|
||||
recordingTrackId =
|
||||
sharedPreferences.getLong(
|
||||
@@ -242,7 +238,7 @@ public class StatsActivity extends Activity
|
||||
-1);
|
||||
checkLiveTrack();
|
||||
restoreStats();
|
||||
updateLocation(null);
|
||||
showUnknownLocation();
|
||||
} else if (key.equals(getString(R.string.metric_units_key))) {
|
||||
metricUnits =
|
||||
sharedPreferences.getBoolean(
|
||||
@@ -299,29 +295,36 @@ public class StatsActivity extends Activity
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChanged(Location l) {
|
||||
if (selectedTrackIsRecording()) {
|
||||
updateLocation(l);
|
||||
private final LocationListener locationListener = new LocationListener() {
|
||||
@Override
|
||||
public void onLocationChanged(Location l) {
|
||||
if (selectedTrackIsRecording()) {
|
||||
showLocation(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderDisabled(String provider) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderEnabled(String provider) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers to receive location updates from the GPS location provider.
|
||||
*/
|
||||
private void registerLocationListener() {
|
||||
LocationManager locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null) {
|
||||
LocationProvider gpsProvider =
|
||||
locationManager.getProvider(MyTracksConstants.GPS_PROVIDER);
|
||||
@@ -334,7 +337,7 @@ public class StatsActivity extends Activity
|
||||
+ gpsProvider.getName());
|
||||
}
|
||||
locationManager.requestLocationUpdates(gpsProvider.getName(),
|
||||
0/*minTime*/, 0/*minDist*/, this);
|
||||
0/*minTime*/, 0/*minDist*/, locationListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +345,10 @@ public class StatsActivity extends Activity
|
||||
* Unregisters all location listener.
|
||||
*/
|
||||
private void unregisterLocationListener() {
|
||||
LocationManager locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
if (locationManager != null) {
|
||||
locationManager.removeUpdates(this);
|
||||
locationManager.removeUpdates(locationListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +369,7 @@ public class StatsActivity extends Activity
|
||||
return;
|
||||
}
|
||||
|
||||
track = providerUtils.getTrack(selectedTrackId);
|
||||
Track track = providerUtils.getTrack(selectedTrackId);
|
||||
if (track == null || track.getStatistics() == null) {
|
||||
utils.setAllToUnknown();
|
||||
return;
|
||||
@@ -421,17 +426,17 @@ public class StatsActivity extends Activity
|
||||
*
|
||||
* @param l may be null (will set location fields to unknown)
|
||||
*/
|
||||
private void updateLocation(Location l) {
|
||||
if (l == null) {
|
||||
utils.setUnknown(R.id.elevation_register);
|
||||
utils.setUnknown(R.id.latitude_register);
|
||||
utils.setUnknown(R.id.longitude_register);
|
||||
utils.setUnknown(R.id.speed_register);
|
||||
} else {
|
||||
utils.setAltitude(R.id.elevation_register, l.getAltitude());
|
||||
utils.setLatLong(R.id.latitude_register, l.getLatitude());
|
||||
utils.setLatLong(R.id.longitude_register, l.getLongitude());
|
||||
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
|
||||
}
|
||||
private void showLocation(Location l) {
|
||||
utils.setAltitude(R.id.elevation_register, l.getAltitude());
|
||||
utils.setLatLong(R.id.latitude_register, l.getLatitude());
|
||||
utils.setLatLong(R.id.longitude_register, l.getLongitude());
|
||||
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
|
||||
}
|
||||
|
||||
private void showUnknownLocation() {
|
||||
utils.setUnknown(R.id.elevation_register);
|
||||
utils.setUnknown(R.id.latitude_register);
|
||||
utils.setUnknown(R.id.longitude_register);
|
||||
utils.setUnknown(R.id.speed_register);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
|
||||
/**
|
||||
* Various utility functions for views that display statistics information.
|
||||
*
|
||||
@@ -218,7 +218,7 @@ public class StatsUtilities {
|
||||
setGrade(R.id.max_grade_register, maxGrade);
|
||||
}
|
||||
|
||||
public void setAllStats(TripStatistics stats) {
|
||||
public void setAllStats(TripStatistics stats) {
|
||||
setTime(R.id.moving_time_register, stats.getMovingTime());
|
||||
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
|
||||
setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6);
|
||||
@@ -230,6 +230,8 @@ public class StatsUtilities {
|
||||
setAltitude(R.id.elevation_gain_register, stats.getTotalElevationGain());
|
||||
setGrade(R.id.min_grade_register, stats.getMinGrade());
|
||||
setGrade(R.id.max_grade_register, stats.getMaxGrade());
|
||||
setTime(R.id.total_time_register, stats.getTotalTime());
|
||||
|
||||
}
|
||||
|
||||
public void setSpeedLabel(int id, int speedString, int paceString) {
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility to access data from the mytracks content provider.
|
||||
*
|
||||
@@ -173,7 +175,7 @@ public interface MyTracksProviderUtils {
|
||||
* @return A cursor over the selected range of locations
|
||||
*/
|
||||
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||
long maxWaypoints);
|
||||
int maxWaypoints);
|
||||
|
||||
/**
|
||||
* Finds a track by given unique track id.
|
||||
@@ -184,6 +186,15 @@ public interface MyTracksProviderUtils {
|
||||
* @return a Track object, or null if not found
|
||||
*/
|
||||
Track getTrack(long id);
|
||||
|
||||
/**
|
||||
* Retrieves all tracks without track points. If no tracks exist an empty
|
||||
* list will be returned. Use {@link #getTrackPoints(Track, int)} to load
|
||||
* the track points.
|
||||
*
|
||||
* @return a list of all the recorded tracks
|
||||
*/
|
||||
List<Track> getAllTracks();
|
||||
|
||||
/**
|
||||
* Loads the track points for a given track.
|
||||
@@ -195,17 +206,6 @@ public interface MyTracksProviderUtils {
|
||||
*/
|
||||
long getTrackPoints(Track track, int maxPoints);
|
||||
|
||||
/**
|
||||
* Fetches some number of locations for the given track.
|
||||
*
|
||||
* This is designed to be used to stream through large tracks without loading
|
||||
* all points into memory.
|
||||
*
|
||||
* @param track to load locations for
|
||||
* @param buffer an array of locations to fill
|
||||
*/
|
||||
void getTrackPoints(Track track, TrackBuffer buffer);
|
||||
|
||||
/**
|
||||
* Creates a cursor over the tracks provider with a given selection.
|
||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||
@@ -294,6 +294,14 @@ public interface MyTracksProviderUtils {
|
||||
*/
|
||||
Location createLocation(Cursor cursor);
|
||||
|
||||
/**
|
||||
* Fill a location object with values from a given cursor.
|
||||
*
|
||||
* @param cursor a cursor pointing at a db or provider with locations
|
||||
* @param location a location object to be overwritten
|
||||
*/
|
||||
void fillLocation(Cursor cursor, Location location);
|
||||
|
||||
/**
|
||||
* Creates a waypoint object from a given cursor.
|
||||
*
|
||||
@@ -337,7 +345,7 @@ public interface MyTracksProviderUtils {
|
||||
* Creates an instance of {@link MyTracksProviderUtils}.
|
||||
*/
|
||||
protected MyTracksProviderUtils newForContext(Context context) {
|
||||
return new MyTracksProviderUtilsImpl(context);
|
||||
return new MyTracksProviderUtilsImpl(context.getContentResolver());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,16 @@ package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.location.Location;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Helper class providing easy access to locations and tracks in the
|
||||
* MyTracksProvider. All static members.
|
||||
@@ -32,10 +35,10 @@ import android.util.Log;
|
||||
*/
|
||||
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
private final Context context;
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
public MyTracksProviderUtilsImpl(Context context) {
|
||||
this.context = context;
|
||||
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
|
||||
this.contentResolver = contentResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,6 +171,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
@Override
|
||||
public Location createLocation(Cursor cursor) {
|
||||
Location location = new Location("");
|
||||
fillLocation(cursor, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillLocation(Cursor cursor, Location location) {
|
||||
location.reset();
|
||||
|
||||
int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
|
||||
int idxLongitude =
|
||||
cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
|
||||
@@ -177,7 +189,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
|
||||
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
|
||||
|
||||
Location location = new Location("");
|
||||
if (!cursor.isNull(idxLatitude)) {
|
||||
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
|
||||
}
|
||||
@@ -199,7 +210,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
if (!cursor.isNull(idxAccuracy)) {
|
||||
location.setAccuracy(cursor.getFloat(idxAccuracy));
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -438,25 +448,22 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
@Override
|
||||
public void deleteAllTracks() {
|
||||
context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null);
|
||||
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||
null, null);
|
||||
context.getContentResolver().delete(
|
||||
WaypointsColumns.CONTENT_URI, null, null);
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
|
||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTrack(long trackId) {
|
||||
Track track = getTrack(trackId);
|
||||
if (track != null) {
|
||||
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI,
|
||||
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
|
||||
null);
|
||||
}
|
||||
context.getContentResolver().delete(WaypointsColumns.CONTENT_URI,
|
||||
contentResolver.delete(WaypointsColumns.CONTENT_URI,
|
||||
WaypointsColumns.TRACKID + "=" + trackId, null);
|
||||
context.getContentResolver().delete(
|
||||
TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
||||
contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -481,7 +488,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
"No statistics marker after the deleted one was found.");
|
||||
}
|
||||
}
|
||||
context.getContentResolver().delete(
|
||||
contentResolver.delete(
|
||||
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
|
||||
}
|
||||
|
||||
@@ -493,7 +500,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(
|
||||
cursor = contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
null /*projection*/,
|
||||
selection,
|
||||
@@ -515,7 +522,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public boolean updateWaypoint(Waypoint waypoint) {
|
||||
try {
|
||||
final int rows = context.getContentResolver().update(
|
||||
final int rows = contentResolver.update(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
createContentValues(waypoint),
|
||||
"_id=" + waypoint.getId(),
|
||||
@@ -536,7 +543,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
private Location findLocationBy(String select) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(
|
||||
cursor = contentResolver.query(
|
||||
TrackPointsColumns.CONTENT_URI, null, select, null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return createLocation(cursor);
|
||||
@@ -560,7 +567,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
private Track findTrackBy(String select) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(
|
||||
cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, null, select, null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
return createTrack(cursor);
|
||||
@@ -582,7 +589,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
@Override
|
||||
public Waypoint getFirstWaypoint(long trackId) {
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
null /*projection*/,
|
||||
"trackid=" + trackId,
|
||||
@@ -604,7 +611,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
|
||||
@Override
|
||||
public Waypoint getWaypoint(long waypointId) {
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
null /*projection*/,
|
||||
"_id=" + waypointId,
|
||||
@@ -627,7 +634,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public long getLastLocationId(long trackId) {
|
||||
final String[] projection = {"_id"};
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
TrackPointsColumns.CONTENT_URI,
|
||||
projection,
|
||||
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
|
||||
@@ -651,7 +658,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public long getFirstWaypointId(long trackId) {
|
||||
final String[] projection = {"_id"};
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
projection,
|
||||
"trackid=" + trackId,
|
||||
@@ -675,7 +682,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public long getLastWaypointId(long trackId) {
|
||||
final String[] projection = {"_id"};
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI,
|
||||
projection,
|
||||
WaypointsColumns.TRACKID + "=" + trackId,
|
||||
@@ -700,7 +707,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
public Track getLastTrack() {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver().query(
|
||||
cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
|
||||
null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
@@ -719,7 +726,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public long getLastTrackId() {
|
||||
String[] proj = { TracksColumns._ID };
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
|
||||
null, null);
|
||||
if (cursor != null) {
|
||||
@@ -759,13 +766,13 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
sortOrder += " LIMIT " + maxLocations;
|
||||
}
|
||||
|
||||
return context.getContentResolver().query(
|
||||
return contentResolver.query(
|
||||
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||
long maxWaypoints) {
|
||||
int maxWaypoints) {
|
||||
String selection;
|
||||
if (minWaypointId > 0) {
|
||||
selection = String.format("%s=%d AND %s>=%d",
|
||||
@@ -781,7 +788,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
sortOrder += " LIMIT " + maxWaypoints;
|
||||
}
|
||||
|
||||
return context.getContentResolver().query(
|
||||
return contentResolver.query(
|
||||
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||
}
|
||||
|
||||
@@ -790,6 +797,21 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
String select = TracksColumns._ID + "=" + id;
|
||||
return findTrackBy(select);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Track> getAllTracks() {
|
||||
Cursor cursor = getTracksCursor(null);
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
return new ArrayList<Track>();
|
||||
}
|
||||
|
||||
List<Track> tracks = new ArrayList<Track>(cursor.getCount());
|
||||
do {
|
||||
tracks.add(createTrack(cursor));
|
||||
} while(cursor.moveToNext());
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTrackPoints(Track track, int maxPoints) {
|
||||
@@ -820,56 +842,9 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
return lastId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTrackPoints(Track track, TrackBuffer buffer) {
|
||||
long startingPoint = buffer.getLastLocationRead() == 0 ? track.getStartId()
|
||||
: buffer.getLastLocationRead();
|
||||
buffer.reset();
|
||||
Cursor cursor = getLocationsCursor(track.getId(),
|
||||
startingPoint,
|
||||
buffer.getSize(), false);
|
||||
if (cursor == null) {
|
||||
Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!");
|
||||
buffer.setInvalid();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (cursor.getCount() == 0) {
|
||||
Log.w(MyTracksProvider.TAG, "No matching locations found.");
|
||||
buffer.resetAt(startingPoint + buffer.getSize());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cursor.moveToFirst()) {
|
||||
Log.w(MyTracksProvider.TAG, "Could not move to first.");
|
||||
buffer.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
final int idColumnIdx =
|
||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||
do {
|
||||
Location location = createLocation(cursor);
|
||||
if (location == null) {
|
||||
continue;
|
||||
}
|
||||
buffer.add(location, cursor.getLong(idColumnIdx));
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
if (buffer.getLocationsLoaded() == 0) {
|
||||
Log.w(MyTracksProvider.TAG, "No locations read.");
|
||||
buffer.resetAt(startingPoint + buffer.getSize());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getTracksCursor(String selection) {
|
||||
Cursor cursor = context.getContentResolver().query(
|
||||
Cursor cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
||||
return cursor;
|
||||
}
|
||||
@@ -877,14 +852,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public Uri insertTrack(Track track) {
|
||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
|
||||
return context.getContentResolver().insert(TracksColumns.CONTENT_URI,
|
||||
return contentResolver.insert(TracksColumns.CONTENT_URI,
|
||||
createContentValues(track));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insertTrackPoint(Location location, long trackId) {
|
||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
|
||||
return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI,
|
||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
|
||||
createContentValues(location, trackId));
|
||||
}
|
||||
|
||||
@@ -897,14 +872,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
values[i] = createContentValues(locations[i], trackId);
|
||||
}
|
||||
|
||||
return context.getContentResolver().bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insertWaypoint(Waypoint waypoint) {
|
||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
|
||||
waypoint.setId(-1);
|
||||
return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI,
|
||||
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
|
||||
createContentValues(waypoint));
|
||||
}
|
||||
|
||||
@@ -913,7 +888,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
final String[] projection = { TracksColumns._ID };
|
||||
cursor = context.getContentResolver().query(
|
||||
cursor = contentResolver.query(
|
||||
TracksColumns.CONTENT_URI,
|
||||
projection,
|
||||
TracksColumns._ID + "=" + id/*selection*/,
|
||||
@@ -933,7 +908,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||
@Override
|
||||
public void updateTrack(Track track) {
|
||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
|
||||
context.getContentResolver().update(TracksColumns.CONTENT_URI,
|
||||
contentResolver.update(TracksColumns.CONTENT_URI,
|
||||
createContentValues(track), "_id=" + track.getId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.content;
|
||||
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
|
||||
import android.location.Location;
|
||||
|
||||
/**
|
||||
* A buffer of Locations from a track.
|
||||
* This class contains some additional information about the loaded points.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TrackBuffer {
|
||||
|
||||
/**
|
||||
* An array of location objects that are part of a track.
|
||||
*/
|
||||
private final Location[] buffer;
|
||||
|
||||
/**
|
||||
* The id of the last location read from the track.
|
||||
*/
|
||||
private long lastLocationRead = 0;
|
||||
|
||||
/**
|
||||
* The number of locations loaded into the buffer.
|
||||
*/
|
||||
private int locationsLoaded = 0;
|
||||
|
||||
/**
|
||||
* Create a TrackBuffer with size elements.
|
||||
*
|
||||
* @param size The size of the buffer
|
||||
*/
|
||||
public TrackBuffer(int size) {
|
||||
buffer = new Location[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the track to a state with no locations.
|
||||
*/
|
||||
public void reset() {
|
||||
resetAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the track at the given starting location id.
|
||||
*/
|
||||
public void resetAt(long lastLocation) {
|
||||
lastLocationRead = lastLocation;
|
||||
locationsLoaded = 0;
|
||||
}
|
||||
|
||||
public void setInvalid() {
|
||||
lastLocationRead = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of locations that can be stored in this buffer
|
||||
*/
|
||||
public int getSize() {
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index The index of the location to fetch
|
||||
* @return The location for the given index
|
||||
*/
|
||||
public Location get(int index) {
|
||||
return buffer[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a location to the end of the buffer.
|
||||
* @param location The location to add.
|
||||
* @param id The id of the location to be added.
|
||||
*/
|
||||
public void add(Location location, long id) {
|
||||
buffer[locationsLoaded++] = location;
|
||||
lastLocationRead = Math.max(lastLocationRead, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the last location loaded into the buffer
|
||||
*/
|
||||
public long getLastLocationRead() {
|
||||
return lastLocationRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of locations loaded into the buffer.
|
||||
*/
|
||||
public int getLocationsLoaded() {
|
||||
return locationsLoaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the start location, i.e. the one which is the first point of a
|
||||
* segment with at least two points.
|
||||
*
|
||||
* @return the start location
|
||||
*/
|
||||
public Location findStartLocation() {
|
||||
int numValidLocations = 0;
|
||||
for (int i = 0; i < getLocationsLoaded(); i++) {
|
||||
Location location = buffer[i];
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
numValidLocations++;
|
||||
if (numValidLocations == 2) {
|
||||
return buffer[i - 1];
|
||||
}
|
||||
} else {
|
||||
numValidLocations = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -468,8 +468,7 @@ public class GpxImporter extends DefaultHandler {
|
||||
|
||||
// initialize start time with time of first track point
|
||||
if (statsBuilder == null) {
|
||||
statsBuilder = new TripStatisticsBuilder();
|
||||
statsBuilder.resumeAt(time);
|
||||
statsBuilder = new TripStatisticsBuilder(time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ public class SendToDocs {
|
||||
androidClient = GDataClientFactory.getGDataClient(activity);
|
||||
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
wiseWrapper.setClient(androidClient, gdataClient);
|
||||
wiseWrapper.setClient(gdataClient);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"GData connection prepared: " + this.writelyAuth);
|
||||
String sheetTitle = "My Tracks";
|
||||
@@ -220,7 +220,7 @@ public class SendToDocs {
|
||||
|
||||
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||
writelyWrapper.setClient(androidClient, docsGdataClient);
|
||||
writelyWrapper.setClient(docsGdataClient);
|
||||
|
||||
// First try to find the spreadsheet:
|
||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* A class to clean up old temporary files.
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TempFileCleaner {
|
||||
|
||||
private long currentTimeMillis;
|
||||
|
||||
public static void clean() {
|
||||
(new TempFileCleaner(System.currentTimeMillis())).cleanImpl();
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
TempFileCleaner(long time) {
|
||||
currentTimeMillis = time;
|
||||
}
|
||||
|
||||
private void cleanImpl() {
|
||||
if (!Environment.getExternalStorageState().equals(
|
||||
Environment.MEDIA_MOUNTED)) {
|
||||
return; // Can't do anything now.
|
||||
}
|
||||
cleanTmpDirectory("csv");
|
||||
cleanTmpDirectory("gpx");
|
||||
cleanTmpDirectory("kml");
|
||||
cleanTmpDirectory("tcx");
|
||||
}
|
||||
|
||||
private void cleanTmpDirectory(String name) {
|
||||
String sep = System.getProperty("file.separator");
|
||||
cleanTmpDirectory(
|
||||
new File(
|
||||
Environment.getExternalStorageDirectory() + sep + name + sep
|
||||
+ "tmp"));
|
||||
}
|
||||
|
||||
// @VisibleForTesting
|
||||
int cleanTmpDirectory(File dir) {
|
||||
if (!dir.exists()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
long oldest = currentTimeMillis - 1000 * 3600;
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.lastModified() < oldest) {
|
||||
f.delete();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package com.google.android.apps.mytracks.io;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||
import com.google.android.apps.mytracks.content.Track;
|
||||
import com.google.android.apps.mytracks.content.TrackBuffer;
|
||||
import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.util.FileUtils;
|
||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||
@@ -254,55 +253,8 @@ public class TrackWriter {
|
||||
void writeDocument() {
|
||||
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
||||
writer.writeHeader();
|
||||
TrackBuffer buffer = new TrackBuffer(1024);
|
||||
Location last = null;
|
||||
boolean wroteFirst = false;
|
||||
boolean segmentOpen = false;
|
||||
int nValidLocations = 0;
|
||||
|
||||
// Fetch small pieces of the track.
|
||||
while (buffer.getLastLocationRead() < track.getStopId()) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Reading track points starting at: " + buffer.getLastLocationRead());
|
||||
providerUtils.getTrackPoints(track, buffer);
|
||||
if (!wroteFirst) {
|
||||
Location first = buffer.findStartLocation();
|
||||
writer.writeBeginTrack(first);
|
||||
wroteFirst = true;
|
||||
}
|
||||
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Reading " + buffer.getLocationsLoaded()
|
||||
+ " Ending at: " + buffer.getLastLocationRead());
|
||||
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
||||
Location location = buffer.get(i);
|
||||
if (MyTracksUtils.isValidLocation(location)) {
|
||||
nValidLocations++;
|
||||
if (!segmentOpen) {
|
||||
writer.writeOpenSegment();
|
||||
segmentOpen = true;
|
||||
}
|
||||
writer.writeLocation(location);
|
||||
if (nValidLocations >= 2) {
|
||||
last = location;
|
||||
}
|
||||
} else {
|
||||
nValidLocations = 0;
|
||||
last = null;
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
if (wroteFirst) {
|
||||
writer.writeEndTrack(last);
|
||||
}
|
||||
// TODO: Fix ordering (in GPX waypoints should come first)
|
||||
writeLocations();
|
||||
writeWaypoints(track.getId());
|
||||
writer.writeFooter();
|
||||
writer.close();
|
||||
@@ -310,4 +262,66 @@ public class TrackWriter {
|
||||
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
||||
errorMessage = R.string.io_write_finished;
|
||||
}
|
||||
|
||||
private void writeLocations() {
|
||||
boolean wroteFirst = false;
|
||||
boolean segmentOpen = false;
|
||||
Location lastLoc = null, loc = null;
|
||||
boolean isLastValid = false;
|
||||
Cursor locationsCursor =
|
||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||
|
||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||
Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
if (loc == null) loc = new Location("");
|
||||
providerUtils.fillLocation(locationsCursor, loc);
|
||||
|
||||
boolean isValid = MyTracksUtils.isValidLocation(loc);
|
||||
boolean validSegment = isValid && isLastValid;
|
||||
if (!wroteFirst && validSegment) {
|
||||
// Found the first two consecutive points which are valid
|
||||
writer.writeBeginTrack(lastLoc);
|
||||
wroteFirst = true;
|
||||
}
|
||||
|
||||
if (validSegment) {
|
||||
if (!segmentOpen) {
|
||||
// Start a segment for this point
|
||||
writer.writeOpenSegment();
|
||||
segmentOpen = true;
|
||||
|
||||
// Write the previous point, which we had previously skipped
|
||||
writer.writeLocation(lastLoc);
|
||||
}
|
||||
|
||||
// Write the current point
|
||||
writer.writeLocation(loc);
|
||||
} else {
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Swap loc and lastLoc (so lastLoc is reused)
|
||||
Location tmp = lastLoc;
|
||||
lastLoc = loc;
|
||||
loc = tmp;
|
||||
if (loc != null) loc.reset();
|
||||
|
||||
isLastValid = isValid;
|
||||
} while (locationsCursor.moveToNext());
|
||||
|
||||
if (segmentOpen) {
|
||||
writer.writeCloseSegment();
|
||||
segmentOpen = false;
|
||||
}
|
||||
if (wroteFirst) {
|
||||
writer.writeEndTrack(lastLoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class TrackWriterFactory {
|
||||
* @param format the output format to write in
|
||||
* @return the new track writer
|
||||
*/
|
||||
public static TrackWriter newWriter(Context context,
|
||||
private static TrackWriter newWriter(Context context,
|
||||
MyTracksProviderUtils providerUtils,
|
||||
Track track, TrackFileFormat format) {
|
||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.io.backup;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
|
||||
import android.app.backup.BackupManager;
|
||||
import android.content.Context;
|
||||
@@ -66,8 +66,9 @@ public abstract class BackupPreferencesListener
|
||||
/**
|
||||
* Creates and returns a proper instance of the listener for this device.
|
||||
*/
|
||||
public static BackupPreferencesListener create(Context context) {
|
||||
if (MyTracksConstants.ANDROID_API_LEVEL >= 8) {
|
||||
public static BackupPreferencesListener create(
|
||||
Context context, ApiFeatures apiFeatures) {
|
||||
if (apiFeatures.hasBackup()) {
|
||||
return new BackupPreferencesListenerImpl(context);
|
||||
} else {
|
||||
return new DummyBackupPreferencesListener();
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.google.android.apps.mytracks.io.gdata;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.io.AuthManager;
|
||||
import com.google.wireless.gdata.client.GDataClient;
|
||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||
import com.google.wireless.gdata.client.HttpException;
|
||||
import com.google.wireless.gdata.parser.ParseException;
|
||||
@@ -28,6 +27,10 @@ import android.util.Log;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
@@ -83,44 +86,64 @@ public class GDataWrapper {
|
||||
public static final int ERROR_CLEANED_UP = 7;
|
||||
// An unknown error occurred.
|
||||
public static final int ERROR_UNKNOWN = 100;
|
||||
|
||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES = 1;
|
||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT = 5000;
|
||||
|
||||
private String errorMessage;
|
||||
private int errorType;
|
||||
private GDataClient androidGDataClient;
|
||||
private GDataServiceClient gdataServiceClient;
|
||||
private AuthManager auth;
|
||||
private boolean retryOnAuthFailure;
|
||||
private int retriesPending;
|
||||
private boolean cleanupCalled;
|
||||
|
||||
|
||||
public GDataWrapper() {
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
auth = null;
|
||||
retryOnAuthFailure = false;
|
||||
retriesPending = 0;
|
||||
cleanupCalled = false;
|
||||
}
|
||||
|
||||
public void setClient(GDataClient androidGDataClient,
|
||||
GDataServiceClient gdataServiceClient) {
|
||||
this.androidGDataClient = androidGDataClient;
|
||||
public void setClient(GDataServiceClient gdataServiceClient) {
|
||||
this.gdataServiceClient = gdataServiceClient;
|
||||
}
|
||||
|
||||
public boolean runAuthenticatedFunction(
|
||||
final AuthenticatedFunction function) {
|
||||
return runOne(function, null);
|
||||
return runCommon(function, null);
|
||||
}
|
||||
|
||||
public boolean runQuery(final QueryFunction query) {
|
||||
return runOne(null, query);
|
||||
return runCommon(null, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an arbitrary piece of code.
|
||||
*/
|
||||
private boolean runOne(final AuthenticatedFunction function,
|
||||
private boolean runCommon(final AuthenticatedFunction function,
|
||||
final QueryFunction query) {
|
||||
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
|
||||
runOne(function, query);
|
||||
if (errorType == ERROR_NO_ERROR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
if (errorType == ERROR_AUTH && auth != null) {
|
||||
if (!retryOnAuthFailure || !invalidateAndRefreshAuthToken()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(MyTracksConstants.TAG, "retrying function/query");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a given function or query. If one is executed, errorType and
|
||||
* errorMessage will contain the result/status of the function/query.
|
||||
*/
|
||||
private void runOne(final AuthenticatedFunction function,
|
||||
final QueryFunction query) {
|
||||
try {
|
||||
if (function != null) {
|
||||
@@ -128,11 +151,14 @@ public class GDataWrapper {
|
||||
} else if (query != null) {
|
||||
query.query(gdataServiceClient);
|
||||
} else {
|
||||
return false;
|
||||
throw new IllegalArgumentException(
|
||||
"invalid invocation of runOne; one of function/query " +
|
||||
"must be non-null");
|
||||
}
|
||||
|
||||
errorType = ERROR_NO_ERROR;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
|
||||
} catch (AuthenticationException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||
errorType = ERROR_AUTH;
|
||||
@@ -166,32 +192,42 @@ public class GDataWrapper {
|
||||
errorType = ERROR_CONFLICT;
|
||||
errorMessage = e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
||||
if (errorType == ERROR_AUTH && auth != null) {
|
||||
Runnable whenFinished = null;
|
||||
if (retryOnAuthFailure) {
|
||||
retriesPending++;
|
||||
// This is a little odd. This method cannot return in the the thread
|
||||
// that called the original runQuery(). We should look at reworking this
|
||||
// API.
|
||||
whenFinished = new Runnable() {
|
||||
public void run() {
|
||||
retriesPending--;
|
||||
try {
|
||||
runOne(function, query);
|
||||
} catch (IllegalStateException ise) {
|
||||
// This can happen if the connection pool was shut down.
|
||||
Log.e(MyTracksConstants.TAG, "Failed to rerun query.", ise);
|
||||
}
|
||||
if (cleanupCalled && retriesPending == 0) {
|
||||
cleanUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
auth.invalidateAndRefresh(whenFinished);
|
||||
/**
|
||||
* Invalidates and refreshes the auth token. Blocks until the refresh has
|
||||
* completed or until we deem the refresh as having timed out.
|
||||
*
|
||||
* @return true If the invalidate/refresh succeeds, false if it fails or
|
||||
* times out.
|
||||
*/
|
||||
private boolean invalidateAndRefreshAuthToken() {
|
||||
Log.d(MyTracksConstants.TAG, "Retrying due to auth failure");
|
||||
// This FutureTask doesn't do anything -- it exists simply to be
|
||||
// blocked upon using get().
|
||||
FutureTask<?> whenFinishedFuture = new FutureTask<Object>(new Runnable() {
|
||||
public void run() {}
|
||||
}, null);
|
||||
|
||||
auth.invalidateAndRefresh(whenFinishedFuture);
|
||||
|
||||
try {
|
||||
Log.d(MyTracksConstants.TAG, "waiting for invalidate");
|
||||
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
Log.d(MyTracksConstants.TAG, "invalidate finished");
|
||||
return true;
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
||||
} catch (ExecutionException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
||||
} catch (TimeoutException e) {
|
||||
Log.e(MyTracksConstants.TAG, "Invalidate didn't complete in time", e);
|
||||
} finally {
|
||||
whenFinishedFuture.cancel(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -203,21 +239,10 @@ public class GDataWrapper {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
// cleanUp must be called when done using this wrapper to close the client.
|
||||
// Note that the cleanup will be delayed if auth failure retries were
|
||||
// requested and there is a pending retry.
|
||||
public void cleanUp() {
|
||||
cleanupCalled = true;
|
||||
if (retriesPending == 0) {
|
||||
androidGDataClient.close();
|
||||
androidGDataClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setAuthManager(AuthManager auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
|
||||
public void setRetryOnAuthFailure(boolean retry) {
|
||||
retryOnAuthFailure = retry;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Creates a default track name based on the current default track name policy.
|
||||
*
|
||||
* @author Matthew Simmons
|
||||
*/
|
||||
class DefaultTrackNameFactory {
|
||||
private static final String TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm";
|
||||
|
||||
private final Context context;
|
||||
|
||||
DefaultTrackNameFactory(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new track name.
|
||||
*
|
||||
* @param trackId The ID for the current track.
|
||||
* @param startTime The start time, in milliseconds since the epoch, of the
|
||||
* current track.
|
||||
* @return The new track name.
|
||||
*/
|
||||
String newTrackName(long trackId, long startTime) {
|
||||
if (useTimestampTrackName()) {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(TIMESTAMP_DATE_FORMAT);
|
||||
return formatter.format(new Date(startTime));
|
||||
} else {
|
||||
return String.format(context.getString(R.string.new_track), trackId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines whether the preferences allow a timestamp-based track name */
|
||||
protected boolean useTimestampTrackName() {
|
||||
SharedPreferences prefs =
|
||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
return prefs.getBoolean(
|
||||
context.getString(R.string.timestamp_track_name_key), true);
|
||||
}
|
||||
}
|
||||
@@ -84,13 +84,4 @@ interface ITrackRecordingService {
|
||||
* Deletes all the stored tracks.
|
||||
*/
|
||||
void deleteAllTracks();
|
||||
|
||||
/**
|
||||
* Notifies the service that its preferences may have been changed.
|
||||
* This is necessary because the service running on a separate process cannot
|
||||
* listen to the changes itself.
|
||||
*
|
||||
* @param key the preference key which may have changed
|
||||
*/
|
||||
void sharedPreferenceChanged(in String key);
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics.
|
||||
* This class will periodically announce the user's trip statistics.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class PeriodicTaskExecuter {
|
||||
|
||||
private final PeriodicTask task;
|
||||
private final TrackRecordingService service;
|
||||
|
||||
/**
|
||||
* A timer to schedule the announcements.
|
||||
* This is non-null if the task is in started (scheduled) state.
|
||||
*/
|
||||
private Timer timer = new Timer();
|
||||
|
||||
private final PeriodicTask task;
|
||||
private Timer timer;
|
||||
|
||||
public PeriodicTaskExecuter(PeriodicTask task,
|
||||
TrackRecordingService service) {
|
||||
@@ -52,8 +52,19 @@ public class PeriodicTaskExecuter {
|
||||
* @param interval The interval in milliseconds
|
||||
*/
|
||||
public void scheduleTask(long interval) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
// TODO: Decouple service from this class once and forever.
|
||||
if (!service.isRecording()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
} else {
|
||||
// First start, or we were previously shut down.
|
||||
task.start();
|
||||
}
|
||||
|
||||
timer = new Timer();
|
||||
if (interval <= 0) {
|
||||
return;
|
||||
@@ -61,12 +72,14 @@ public class PeriodicTaskExecuter {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long next = service.getTripStatistics().getStartTime();
|
||||
while (next < now) next += interval;
|
||||
if (next < now) {
|
||||
next = now + interval - ((now - next) % interval);
|
||||
}
|
||||
|
||||
Date start = new Date(next);
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"StatusAnnouncer scheduled to start at " + start + " every "
|
||||
+ interval + " milliseconds.");
|
||||
task.getClass().getSimpleName() + " scheduled to start at " + start
|
||||
+ " every " + interval + " milliseconds.");
|
||||
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
||||
}
|
||||
|
||||
@@ -74,10 +87,14 @@ public class PeriodicTaskExecuter {
|
||||
* Cleans up this object.
|
||||
*/
|
||||
public void shutdown() {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
task.shutdown();
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
task.getClass().getSimpleName() + " shutting down.");
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
timer = null;
|
||||
task.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,21 +15,25 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A class that manages reading the shared preferences for the service.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class PreferenceManager {
|
||||
public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
||||
private TrackRecordingService service;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private final String announcementFrequencyKey;
|
||||
private final String autoResumeTrackCurrentRetryKey;
|
||||
private final String autoResumeTrackTimeoutKey;
|
||||
private final String maxRecordingDistanceKey;
|
||||
private final String metricUnitsKey;
|
||||
private final String minRecordingDistanceKey;
|
||||
@@ -41,9 +45,21 @@ public class PreferenceManager {
|
||||
|
||||
public PreferenceManager(TrackRecordingService service) {
|
||||
this.service = service;
|
||||
|
||||
this.sharedPreferences = service.getSharedPreferences(
|
||||
MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (sharedPreferences == null) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Couldn't get shared preferences.");
|
||||
throw new IllegalStateException("Couldn't get shared preferences");
|
||||
}
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
announcementFrequencyKey =
|
||||
service.getString(R.string.announcement_frequency_key);
|
||||
autoResumeTrackCurrentRetryKey =
|
||||
service.getString(R.string.auto_resume_track_current_retry_key);
|
||||
autoResumeTrackTimeoutKey =
|
||||
service.getString(R.string.auto_resume_track_timeout_key);
|
||||
maxRecordingDistanceKey =
|
||||
service.getString(R.string.max_recording_distance_key);
|
||||
metricUnitsKey =
|
||||
@@ -54,12 +70,15 @@ public class PreferenceManager {
|
||||
service.getString(R.string.min_recording_interval_key);
|
||||
minRequiredAccuracyKey =
|
||||
service.getString(R.string.min_required_accuracy_key);
|
||||
splitFrequencyKey =
|
||||
service.getString(R.string.split_frequency_key);
|
||||
signalSamplingFrequencyKey =
|
||||
service.getString(R.string.signal_sampling_frequency_key);
|
||||
recordingTrackKey =
|
||||
service.getString(R.string.recording_track_key);
|
||||
signalSamplingFrequencyKey =
|
||||
service.getString(R.string.signal_sampling_frequency_key);
|
||||
splitFrequencyKey =
|
||||
service.getString(R.string.split_frequency_key);
|
||||
|
||||
// Refresh all properties.
|
||||
onSharedPreferenceChanged(sharedPreferences, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,15 +87,15 @@ public class PreferenceManager {
|
||||
*
|
||||
* @param key the key that changed (may be null to update all preferences)
|
||||
*/
|
||||
public void onSharedPreferenceChanged(String key) {
|
||||
SharedPreferences sharedPreferences =
|
||||
service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
if (sharedPreferences == null) {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
if (service == null) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Couldn't get shared preferences.");
|
||||
"onSharedPreferenceChanged: a preference change (key = " + key
|
||||
+ ") after a call to shutdown()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == null || key.equals(minRecordingDistanceKey)) {
|
||||
service.setMinRecordingDistance(
|
||||
sharedPreferences.getInt(
|
||||
@@ -87,10 +106,9 @@ public class PreferenceManager {
|
||||
+ service.getMinRecordingDistance());
|
||||
}
|
||||
if (key == null || key.equals(maxRecordingDistanceKey)) {
|
||||
service.setMaxRecordingDistance(
|
||||
sharedPreferences.getInt(
|
||||
maxRecordingDistanceKey,
|
||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE));
|
||||
service.setMaxRecordingDistance(sharedPreferences.getInt(
|
||||
maxRecordingDistanceKey,
|
||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE));
|
||||
}
|
||||
if (key == null || key.equals(minRecordingIntervalKey)) {
|
||||
int minRecordingInterval = sharedPreferences.getInt(
|
||||
@@ -119,19 +137,27 @@ public class PreferenceManager {
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(minRequiredAccuracyKey)) {
|
||||
service.setMinRequiredAccuracy(
|
||||
sharedPreferences.getInt(
|
||||
minRequiredAccuracyKey,
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY));
|
||||
service.setMinRequiredAccuracy(sharedPreferences.getInt(
|
||||
minRequiredAccuracyKey,
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY));
|
||||
}
|
||||
if (key == null || key.equals(announcementFrequencyKey)) {
|
||||
service.setAnnouncementFrequency(
|
||||
sharedPreferences.getInt(announcementFrequencyKey,
|
||||
-1));
|
||||
sharedPreferences.getInt(announcementFrequencyKey, -1));
|
||||
}
|
||||
if (key == null || key.equals(autoResumeTrackTimeoutKey)) {
|
||||
service.setAutoResumeTrackTimeout(sharedPreferences.getInt(
|
||||
autoResumeTrackTimeoutKey,
|
||||
MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT));
|
||||
}
|
||||
if (key == null || key.equals(recordingTrackKey)) {
|
||||
service.setRecordingTrackId(
|
||||
sharedPreferences.getLong(recordingTrackKey, -1));
|
||||
long recordingTrackId = sharedPreferences.getLong(recordingTrackKey, -1);
|
||||
// Only read the id if it is valid.
|
||||
// Setting it to -1 should only happen in
|
||||
// TrackRecordingService.endCurrentTrack()
|
||||
if (recordingTrackId > 0) {
|
||||
service.setRecordingTrackId(recordingTrackId);
|
||||
}
|
||||
}
|
||||
if (key == null || key.equals(splitFrequencyKey)) {
|
||||
service.getSplitManager().setSplitFrequency(
|
||||
@@ -139,13 +165,30 @@ public class PreferenceManager {
|
||||
}
|
||||
if (key == null || key.equals(signalSamplingFrequencyKey)) {
|
||||
service.getSignalManager().setFrequency(
|
||||
sharedPreferences.getInt(
|
||||
signalSamplingFrequencyKey, -1), service);
|
||||
sharedPreferences.getInt(signalSamplingFrequencyKey, -1), service);
|
||||
}
|
||||
if (key == null || key.equals(metricUnitsKey)) {
|
||||
service.getSplitManager().setMetricUnits(
|
||||
sharedPreferences.getBoolean(
|
||||
metricUnitsKey, true));
|
||||
sharedPreferences.getBoolean(metricUnitsKey, true));
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoResumeTrackCurrentRetry(int retryAttempts) {
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putInt(autoResumeTrackCurrentRetryKey, retryAttempts)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void setRecordingTrack(long id) {
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putLong(recordingTrackKey, id)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
||||
service = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics. This class
|
||||
* is a wrapper for cupcake devices.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class SafeStatusAnnouncerTask implements PeriodicTask {
|
||||
|
||||
private StatusAnnouncerTask announcer;
|
||||
|
||||
/* class initialization fails when this throws an exception */
|
||||
static {
|
||||
try {
|
||||
Class.forName(
|
||||
"com.google.android.apps.mytracks.services.StatusAnnouncerTask");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (LinkageError er) {
|
||||
throw new RuntimeException(er);
|
||||
}
|
||||
}
|
||||
|
||||
/* calling here forces class initialization */
|
||||
public static void checkAvailable() {
|
||||
}
|
||||
|
||||
public SafeStatusAnnouncerTask(Context context) {
|
||||
announcer = new StatusAnnouncerTask(context);
|
||||
}
|
||||
|
||||
public void run(TrackRecordingService service) {
|
||||
announcer.run(service);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
announcer.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,8 @@ public class SignalStrengthTask extends PhoneStateListener
|
||||
public SignalStrengthTask(Context c) {
|
||||
context = c;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
manager =
|
||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
@@ -176,6 +177,7 @@ public class SignalStrengthTask extends PhoneStateListener
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (manager != null) {
|
||||
manager.listen(this, PhoneStateListener.LISTEN_NONE);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Factory for producing a proper {@link SignalStrengthTask} according to the
|
||||
* current API level.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class SignalStrengthTaskFactory {
|
||||
private final boolean hasModernSignalStrength;
|
||||
|
||||
SignalStrengthTaskFactory(ApiFeatures apiFeatures) {
|
||||
this.hasModernSignalStrength = apiFeatures.hasModernSignalStrength();
|
||||
}
|
||||
|
||||
public PeriodicTask create(Context context) {
|
||||
if (hasModernSignalStrength) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService using modern signal strength api.");
|
||||
return new SignalStrengthTaskModern(context);
|
||||
} else {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService using legacy signal strength api.");
|
||||
return new SignalStrengthTask(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,11 @@ public class SplitManager {
|
||||
* Calculates the next distance that a split should be inserted at.
|
||||
*/
|
||||
public void calculateNextSplit() {
|
||||
// TODO: Decouple service from this class once and forever.
|
||||
if (!service.isRecording()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (splitFrequency >= 0) {
|
||||
nextSplitDistance = Double.MAX_VALUE;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
@@ -120,7 +125,15 @@ public class SplitManager {
|
||||
* @param splitFrequency The frequency in time or distance
|
||||
*/
|
||||
public void setSplitFrequency(int splitFrequency) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"setSplitFrequency: splitFrequency = " + splitFrequency);
|
||||
this.splitFrequency = splitFrequency;
|
||||
|
||||
// TODO: Decouple service from this class once and forever.
|
||||
if (!service.isRecording()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (splitFrequency < 1) {
|
||||
if (splitExecuter != null) {
|
||||
splitExecuter.shutdown();
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
/**
|
||||
* Factory which wraps construction and setup of text-to-speech announcements in
|
||||
* an API-level-safe way.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class StatusAnnouncerFactory {
|
||||
|
||||
private final boolean hasTts;
|
||||
|
||||
public StatusAnnouncerFactory(ApiFeatures apiFeatures) {
|
||||
this.hasTts = apiFeatures.hasTextToSpeech();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a periodic task which does voice announcements.
|
||||
*
|
||||
* @return the task, or null if announcements are not supported
|
||||
*/
|
||||
public PeriodicTask create(Context context) {
|
||||
if (hasTts) {
|
||||
return new StatusAnnouncerTask(context);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate volume stream for controlling announcement
|
||||
* volume.
|
||||
*/
|
||||
public int getVolumeStream() {
|
||||
if (hasTts) {
|
||||
return StatusAnnouncerTask.getVolumeStream();
|
||||
} else {
|
||||
return AudioManager.USE_DEFAULT_STREAM_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,22 +26,25 @@ import com.google.android.maps.mytracks.R;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* This class will periodically announce the user's trip statitics.
|
||||
* This class will periodically announce the user's trip statistics.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class StatusAnnouncerTask
|
||||
implements TextToSpeech.OnInitListener, PeriodicTask {
|
||||
public class StatusAnnouncerTask implements PeriodicTask {
|
||||
|
||||
/**
|
||||
* The rate at which announcements are spoken.
|
||||
*/
|
||||
private static final float TTS_SPEECH_RATE = 0.9f;
|
||||
// @VisibleForTesting
|
||||
static final float TTS_SPEECH_RATE = 0.9f;
|
||||
|
||||
/**
|
||||
* A pointer to the service context.
|
||||
@@ -56,42 +59,83 @@ public class StatusAnnouncerTask
|
||||
/**
|
||||
* The interface to the text to speech engine.
|
||||
*/
|
||||
private TextToSpeech tts = null;
|
||||
private TextToSpeech tts;
|
||||
|
||||
/**
|
||||
* The response recieved from the TTS engine ater initialization.
|
||||
* The response received from the TTS engine after initialization.
|
||||
*/
|
||||
private int ttsStatus = TextToSpeech.ERROR;
|
||||
private boolean ready = false;
|
||||
|
||||
/**
|
||||
* Constructs the announcer and start the TTS engine.
|
||||
* Whether we're allowed to speak right now.
|
||||
*/
|
||||
private boolean speechAllowed;
|
||||
|
||||
/**
|
||||
* Listener which updates {@link #speechAllowed} when the phone state changes.
|
||||
*/
|
||||
private final PhoneStateListener phoneListener = new PhoneStateListener() {
|
||||
@Override
|
||||
public void onCallStateChanged(int state, String incomingNumber) {
|
||||
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
|
||||
|
||||
if (!speechAllowed && tts.isSpeaking()) {
|
||||
// If we're already speaking, stop it.
|
||||
tts.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public StatusAnnouncerTask(Context context) {
|
||||
this(context, new StringUtils(context));
|
||||
}
|
||||
|
||||
public StatusAnnouncerTask(Context context, StringUtils stringUtils) {
|
||||
this.context = context;
|
||||
this.stringUtils = new StringUtils(context);
|
||||
|
||||
tts = new TextToSpeech(context, this);
|
||||
// Force the language to be the same as the string we will be speaking.
|
||||
tts.setLanguage(Locale.getDefault());
|
||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||
this.stringUtils = stringUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that the tts engine is done with initialization.
|
||||
* Called when the TTS engine is initialized.
|
||||
*/
|
||||
public void onInit(int status) {
|
||||
this.ttsStatus = status;
|
||||
private void onTtsInit(int status) {
|
||||
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
||||
this.ready = status == TextToSpeech.SUCCESS;
|
||||
|
||||
if (ready) {
|
||||
// Force the language to be the same as the string we will be speaking,
|
||||
// if that's available.
|
||||
Locale speechLanguage = Locale.getDefault();
|
||||
int languageAvailability = tts.isLanguageAvailable(speechLanguage);
|
||||
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA ||
|
||||
languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||
// English is probably supported.
|
||||
// TODO: Somehow use announcement strings from English too.
|
||||
Log.w(MyTracksConstants.TAG, "Default language not available, using English.");
|
||||
speechLanguage = Locale.ENGLISH;
|
||||
}
|
||||
tts.setLanguage(speechLanguage);
|
||||
|
||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Announces the trip status.
|
||||
*/
|
||||
@Override
|
||||
public void run(TrackRecordingService service) {
|
||||
if (ttsStatus != TextToSpeech.SUCCESS && tts != null) {
|
||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not initialized.");
|
||||
if (!ready || tts == null) {
|
||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not ready.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!speechAllowed) {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"Not making announcement - not allowed at this time");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,7 +154,8 @@ public class StatusAnnouncerTask
|
||||
*
|
||||
* @return The string that will be read to the user
|
||||
*/
|
||||
private String getAnnouncement(final TripStatistics stats) {
|
||||
// @VisibleForTesting
|
||||
protected String getAnnouncement(TripStatistics stats) {
|
||||
SharedPreferences preferences =
|
||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
boolean metricUnits = true;
|
||||
@@ -172,11 +217,63 @@ public class StatusAnnouncerTask
|
||||
context.getString(speedLabel));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
tts.shutdown();
|
||||
@Override
|
||||
public void start() {
|
||||
Log.i(MyTracksConstants.TAG, "Starting TTS");
|
||||
if (tts == null) {
|
||||
// We can't have this class also be the listener, otherwise it's unsafe to
|
||||
// reference it in Cupcake (even if we don't instantiate it).
|
||||
tts = newTextToSpeech(context, new OnInitListener() {
|
||||
@Override
|
||||
public void onInit(int status) {
|
||||
onTtsInit(status);
|
||||
}
|
||||
});
|
||||
}
|
||||
speechAllowed = true;
|
||||
|
||||
// Register ourselves as a listener so we won't speak during a call.
|
||||
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
public void shutdown() {
|
||||
// Stop listening to phone state.
|
||||
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_NONE);
|
||||
|
||||
if (tts != null) {
|
||||
tts.shutdown();
|
||||
tts = null;
|
||||
}
|
||||
|
||||
Log.i(MyTracksConstants.TAG, "TTS shut down");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for instantiating a {@link TextToSpeech} object, which causes
|
||||
* several issues during testing.
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
||||
return new TextToSpeech(ctx, onInitListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for calls to the 100%-unmockable {@link TelephonyManager#listen}.
|
||||
*/
|
||||
// @VisibleForTesting
|
||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
||||
TelephonyManager telephony =
|
||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
if (telephony != null) {
|
||||
telephony.listen(listener, events);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the volume stream to use for controlling announcement volume.
|
||||
*/
|
||||
public static int getVolumeStream() {
|
||||
return TextToSpeech.Engine.DEFAULT_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,24 +15,23 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class will manage a period task executer.
|
||||
* This class manages a period task executer.
|
||||
*
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class TaskExecuterManager {
|
||||
|
||||
int frequency;
|
||||
PeriodicTask task;
|
||||
PeriodicTaskExecuter executer;
|
||||
private int frequency;
|
||||
private final PeriodicTask task;
|
||||
private PeriodicTaskExecuter executer;
|
||||
|
||||
public TaskExecuterManager(int frequency,
|
||||
PeriodicTask task,
|
||||
TrackRecordingService service) {
|
||||
public TaskExecuterManager(int frequency, PeriodicTask task,
|
||||
TrackRecordingService service) {
|
||||
this.task = task;
|
||||
setFrequency(frequency, service);
|
||||
}
|
||||
@@ -50,7 +49,8 @@ public class TaskExecuterManager {
|
||||
*/
|
||||
public void setFrequency(int frequency, TrackRecordingService service) {
|
||||
this.frequency = frequency;
|
||||
Log.i(MyTracksConstants.TAG, "Frequency set to: " + frequency);
|
||||
Log.i(MyTracksConstants.TAG, "Frequency set to " + frequency
|
||||
+ " for task " + task.getClass().getSimpleName());
|
||||
|
||||
if (frequency == -1) {
|
||||
if (executer != null) {
|
||||
@@ -61,7 +61,6 @@ public class TaskExecuterManager {
|
||||
}
|
||||
} else {
|
||||
if (executer == null) {
|
||||
task.start();
|
||||
executer = new PeriodicTaskExecuter(task, service);
|
||||
}
|
||||
executer.scheduleTask(frequency * 60000);
|
||||
@@ -69,12 +68,20 @@ public class TaskExecuterManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the task at the current frequency.
|
||||
* Restores the task at the current frequency.
|
||||
*/
|
||||
public void restore() {
|
||||
if (frequency > 0) {
|
||||
task.start();
|
||||
executer.scheduleTask(frequency * 60000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down this executer.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.apps.mytracks.services;
|
||||
|
||||
import static com.google.android.apps.mytracks.MyTracksConstants.RESUME_TRACK_EXTRA_NAME;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracks;
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||
@@ -25,6 +27,8 @@ import com.google.android.apps.mytracks.content.Waypoint;
|
||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||
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.MyTracksUtils;
|
||||
import com.google.android.apps.mytracks.util.StringUtils;
|
||||
import com.google.android.maps.mytracks.R;
|
||||
@@ -36,6 +40,7 @@ import android.app.Service;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.location.Location;
|
||||
@@ -63,6 +68,8 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
private static final String STATISTICS_ICON_URL =
|
||||
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
|
||||
|
||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
||||
|
||||
private NotificationManager notificationManager;
|
||||
private LocationManager locationManager;
|
||||
private WakeLock wakeLock;
|
||||
@@ -73,15 +80,13 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE;
|
||||
private int minRequiredAccuracy =
|
||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
||||
private int autoResumeTrackTimeout =
|
||||
MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT;
|
||||
|
||||
private long recordingTrackId = -1;
|
||||
|
||||
private long currentWaypointId = -1;
|
||||
|
||||
/**
|
||||
* For debugging. Keep track of calls to onCreate().
|
||||
*/
|
||||
private boolean onCreateWasCalled = false;
|
||||
|
||||
/** The timer posts a runnable to the main thread via this handler. */
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
@@ -90,20 +95,20 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
*/
|
||||
private MyTracksProviderUtils providerUtils;
|
||||
|
||||
private TripStatisticsBuilder statsBuilder = new TripStatisticsBuilder();
|
||||
private TripStatisticsBuilder waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
private TripStatisticsBuilder statsBuilder;
|
||||
private TripStatisticsBuilder waypointStatsBuilder;
|
||||
|
||||
/**
|
||||
* Current length of the recorded track. This length is calculated from the
|
||||
* recorded points (as compared to each location fix). It's used to overlay
|
||||
* waypoints precisely in the elevation profile chart.
|
||||
*/
|
||||
private double length = 0;
|
||||
private double length;
|
||||
|
||||
/**
|
||||
* Status announcer executer.
|
||||
*/
|
||||
private PeriodicTaskExecuter executer;
|
||||
private PeriodicTaskExecuter announcementExecuter;
|
||||
private TaskExecuterManager signalManager;
|
||||
private SplitManager splitManager;
|
||||
|
||||
@@ -113,7 +118,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
* The interval in milliseconds that we have requested to be notified of gps
|
||||
* readings.
|
||||
*/
|
||||
private long currentRecordingInterval = 0;
|
||||
private long currentRecordingInterval;
|
||||
|
||||
/**
|
||||
* The policy used to decide how often we should request gps updates.
|
||||
@@ -125,14 +130,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
* Task invoked by a timer periodically to make sure the location listener is
|
||||
* still registered.
|
||||
*/
|
||||
private final TimerTask checkLocationListener = new TimerTask() {
|
||||
private TimerTask checkLocationListener = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!onCreateWasCalled) {
|
||||
Log.e(MyTracksConstants.TAG,
|
||||
"TrackRecordingService is running, but onCreate not called.");
|
||||
}
|
||||
if (isRecording) {
|
||||
// It's always safe to assume that if isRecording() is true, it implies
|
||||
// that onCreate() has finished.
|
||||
if (isRecording()) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
@@ -141,9 +144,6 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
registerLocationListener();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"Track recording service is paused. That should not be.");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -158,38 +158,31 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
*/
|
||||
private boolean isMoving = true;
|
||||
|
||||
/**
|
||||
* The most recent recording track.
|
||||
*/
|
||||
private Track recordingTrack;
|
||||
|
||||
/**
|
||||
* Is the service currently recording a track?
|
||||
*/
|
||||
private boolean isRecording = false;
|
||||
private boolean isRecording;
|
||||
|
||||
/**
|
||||
* Last good location the service has received from the location listener
|
||||
*/
|
||||
private Location lastLocation = null;
|
||||
private Location lastLocation;
|
||||
|
||||
/**
|
||||
* Last valid location (i.e. not a marker) that was recorded.
|
||||
*/
|
||||
private Location lastValidLocation = null;
|
||||
private Location lastValidLocation;
|
||||
|
||||
/**
|
||||
* The frequency of status announcements.
|
||||
*/
|
||||
private int announcementFrequency = -1;
|
||||
|
||||
private static boolean mTTSAvailable;
|
||||
|
||||
/* establish whether the "new" class is available to us */
|
||||
static {
|
||||
try {
|
||||
SafeStatusAnnouncerTask.checkAvailable();
|
||||
mTTSAvailable = true;
|
||||
} catch (Throwable t) {
|
||||
mTTSAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
@@ -302,7 +295,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
* Tries to acquire a partial wake lock if not already acquired. Logs errors
|
||||
* and gives up trying in case the wake lock cannot be acquired.
|
||||
*/
|
||||
public void acquireWakeLock() {
|
||||
private void acquireWakeLock() {
|
||||
try {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm == null) {
|
||||
@@ -333,10 +326,22 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the wake lock if it's currently held.
|
||||
*/
|
||||
private void releaseWakeLock() {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
wakeLock = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the notification message and icon in the notification bar.
|
||||
*/
|
||||
public void showNotification() {
|
||||
final ApiPlatformAdapter apiPlatformAdapter =
|
||||
ApiFeatures.getInstance().getApiPlatformAdapter();
|
||||
if (isRecording) {
|
||||
Notification notification = new Notification(
|
||||
R.drawable.arrow_320, null /* tickerText */,
|
||||
@@ -347,9 +352,10 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
||||
getString(R.string.recording_your_track), contentIntent);
|
||||
notification.flags += Notification.FLAG_NO_CLEAR;
|
||||
notificationManager.notify(1, notification);
|
||||
apiPlatformAdapter.startForeground(this, notificationManager, 1,
|
||||
notification);
|
||||
} else {
|
||||
notificationManager.cancelAll();
|
||||
apiPlatformAdapter.stopForeground(this, notificationManager, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,23 +394,22 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Location listener now unregistered w/ TrackRecordingService.");
|
||||
}
|
||||
|
||||
private void restoreStats() {
|
||||
|
||||
private Track getRecordingTrack() {
|
||||
if (recordingTrackId < 0) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
Track track = providerUtils.getTrack(recordingTrackId);
|
||||
if (track == null) {
|
||||
return;
|
||||
}
|
||||
return providerUtils.getTrack(recordingTrackId);
|
||||
}
|
||||
|
||||
private void restoreStats(Track track) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Restoring stats of track with ID: " + track.getId());
|
||||
|
||||
TripStatistics stats = track.getStatistics();
|
||||
statsBuilder = new TripStatisticsBuilder();
|
||||
statsBuilder.resumeAt(stats.getStartTime());
|
||||
if (executer != null) {
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
|
||||
setUpAnnouncer();
|
||||
|
||||
signalManager.restore();
|
||||
splitManager.restore();
|
||||
@@ -414,11 +419,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
|
||||
if (waypoint != null) {
|
||||
currentWaypointId = waypoint.getId();
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(waypoint.getStatistics());
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(
|
||||
waypoint.getStatistics());
|
||||
} else {
|
||||
// This should never happen, but we got to do something so life goes on:
|
||||
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
waypointStatsBuilder.resumeAt(stats.getStartTime());
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
|
||||
currentWaypointId = -1;
|
||||
}
|
||||
|
||||
@@ -442,7 +447,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
|
||||
statsBuilder.pauseAt(stats.getStopTime());
|
||||
statsBuilder.resume();
|
||||
statsBuilder.resumeAt(System.currentTimeMillis());
|
||||
} else {
|
||||
Log.e(MyTracksConstants.TAG, "Could not get track points cursor.");
|
||||
}
|
||||
@@ -489,7 +494,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
}
|
||||
|
||||
// At least one track must be available for appending points:
|
||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||
recordingTrack = getRecordingTrack();
|
||||
if (recordingTrack == null) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Not recording. No track to append to available.");
|
||||
@@ -504,8 +509,8 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
// Update the idle time if needed.
|
||||
locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
|
||||
if (currentRecordingInterval
|
||||
!= locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
if (currentRecordingInterval !=
|
||||
locationListenerPolicy.getDesiredPollingInterval()) {
|
||||
registerLocationListener();
|
||||
}
|
||||
|
||||
@@ -610,69 +615,89 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* SharedPreferencesChangeListener interface implementation. Note that
|
||||
* services don't currently receive this event (Android platform limitation).
|
||||
* This should be called from an activity whenever settings change.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notifies that preferences have changed.
|
||||
* Call this with key == null to update all preferences in one call.
|
||||
*
|
||||
* @param key the key that changed (may be null to update all preferences)
|
||||
*/
|
||||
public void onSharedPreferenceChanged(String key) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService.onSharedPreferenceChanged");
|
||||
prefManager.onSharedPreferenceChanged(key);
|
||||
|
||||
if (isRecording) {
|
||||
registerLocationListener();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Application lifetime events: ============================
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onCreate");
|
||||
super.onCreate();
|
||||
onCreateWasCalled = true;
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onCreate");
|
||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||
notificationManager =
|
||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
locationManager =
|
||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||
splitManager = new SplitManager(this);
|
||||
try {
|
||||
signalManager =
|
||||
new TaskExecuterManager(-1, new SignalStrengthTaskModern(this), this);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService using modern signal strength api.");
|
||||
} catch (LinkageError e) {
|
||||
Log.w(MyTracksConstants.TAG,
|
||||
"TrackRecordingService could not load modern signal strength.", e);
|
||||
signalManager =
|
||||
new TaskExecuterManager(-1, new SignalStrengthTask(this), this);
|
||||
}
|
||||
|
||||
SignalStrengthTaskFactory strengthTaskFactory =
|
||||
new SignalStrengthTaskFactory(ApiFeatures.getInstance());
|
||||
signalManager =
|
||||
new TaskExecuterManager(-1, strengthTaskFactory.create(this), this);
|
||||
|
||||
prefManager = new PreferenceManager(this);
|
||||
onSharedPreferenceChanged(null);
|
||||
restoreStats();
|
||||
registerLocationListener();
|
||||
acquireWakeLock();
|
||||
/**
|
||||
* After 5 min, check every minute that location listener still is
|
||||
* registered and spit out additional debugging info to the logs:
|
||||
*/
|
||||
timer.schedule(checkLocationListener, 1000 * 60 * 5, 1000 * 60);
|
||||
isRecording = true;
|
||||
if (mTTSAvailable && (announcementFrequency != -1)) {
|
||||
if (executer == null) {
|
||||
SafeStatusAnnouncerTask announcer = new SafeStatusAnnouncerTask(this);
|
||||
executer = new PeriodicTaskExecuter(announcer, this);
|
||||
|
||||
// Try to restore previous recording state in case this service has been
|
||||
// restarted by the system, which can sometimes happen.
|
||||
recordingTrack = getRecordingTrack();
|
||||
if (recordingTrack != null) {
|
||||
restoreStats(recordingTrack);
|
||||
isRecording = true;
|
||||
} else {
|
||||
if (recordingTrackId != -1) {
|
||||
// Make sure we have consistent state in shared preferences.
|
||||
Log.w(MyTracksConstants.TAG, "TrackRecordingService.onCreate: "
|
||||
+ "Resetting an orphaned recording track = " + recordingTrackId);
|
||||
}
|
||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
||||
}
|
||||
showNotification();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Executer} and schedules {@class SafeStatusAnnouncerTask}.
|
||||
* The announcer requires a TTS service and user should have enabled
|
||||
* the announcements, otherwise this method is no-op.
|
||||
*/
|
||||
private void setUpAnnouncer() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.setUpAnnouncer: "
|
||||
+ announcementExecuter);
|
||||
if (announcementFrequency != -1 && recordingTrackId != -1) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (announcementExecuter == null) {
|
||||
StatusAnnouncerFactory statusAnnouncerFactory =
|
||||
new StatusAnnouncerFactory(ApiFeatures.getInstance());
|
||||
PeriodicTask announcer = statusAnnouncerFactory.create(
|
||||
TrackRecordingService.this);
|
||||
if (announcer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Either use TaskExecuterManager everywhere, or get rid of it
|
||||
announcementExecuter = new PeriodicTaskExecuter(announcer,
|
||||
TrackRecordingService.this);
|
||||
}
|
||||
announcementExecuter.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownAnnouncer() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.shutdownAnnouncer: "
|
||||
+ announcementExecuter);
|
||||
if (announcementExecuter != null) {
|
||||
try {
|
||||
announcementExecuter.shutdown();
|
||||
} finally {
|
||||
announcementExecuter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,16 +705,32 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onDestroy");
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
}
|
||||
|
||||
isRecording = false;
|
||||
showNotification();
|
||||
prefManager.shutdown();
|
||||
prefManager = null;
|
||||
checkLocationListener.cancel();
|
||||
checkLocationListener = null;
|
||||
timer.cancel();
|
||||
timer.purge();
|
||||
unregisterLocationListener();
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
}
|
||||
shutdownAnnouncer();
|
||||
signalManager.shutdown();
|
||||
signalManager = null;
|
||||
splitManager.shutdown();
|
||||
splitManager = null;
|
||||
|
||||
// Make sure we have no indirect references to this service.
|
||||
locationManager = null;
|
||||
notificationManager = null;
|
||||
providerUtils = null;
|
||||
binder.detachFromService();
|
||||
binder = null;
|
||||
|
||||
// This should be the last operation.
|
||||
releaseWakeLock();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@@ -711,8 +752,104 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
unregisterLocationListener();
|
||||
return super.stopService(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
handleStartCommand(intent, startId);
|
||||
}
|
||||
|
||||
@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(MyTracksConstants.TAG,
|
||||
"TrackRecordingService.handleStartCommand: " + startId);
|
||||
|
||||
// Check if called on phone reboot with resume intent.
|
||||
if (intent != null &&
|
||||
intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService: requested resume");
|
||||
|
||||
// Make sure that the current track exists and is fresh enough.
|
||||
if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"TrackRecordingService: Not resuming, because the previous track ("
|
||||
+ recordingTrack + ") doesn't exist or is too old");
|
||||
isRecording = false;
|
||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
||||
stopSelfResult(startId);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(MyTracksConstants.TAG, "TrackRecordingService: resuming");
|
||||
}
|
||||
}
|
||||
|
||||
private void setAutoResumeTrackRetries(
|
||||
SharedPreferences sharedPreferences, int retryAttempts) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"Updating auto-resume retry attempts to: " + retryAttempts);
|
||||
prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
|
||||
}
|
||||
|
||||
private boolean shouldResumeTrack(Track track) {
|
||||
Log.d(MyTracksConstants.TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
|
||||
+ autoResumeTrackTimeout);
|
||||
|
||||
// Check if we haven't exceeded the maximum number of retry attempts.
|
||||
SharedPreferences sharedPreferences =
|
||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||
int retries = sharedPreferences.getInt(
|
||||
getString(R.string.auto_resume_track_current_retry_key), 0);
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"shouldResumeTrack: Attempting to auto-resume the track ("
|
||||
+ (retries + 1) + "/" + MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
|
||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"shouldResumeTrack: Not resuming because exceeded the maximum "
|
||||
+ "number of auto-resume retries");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Increase number of retry attempts.
|
||||
setAutoResumeTrackRetries(sharedPreferences, retries + 1);
|
||||
|
||||
// Check for special cases.
|
||||
if (autoResumeTrackTimeout == 0) {
|
||||
// Never resume.
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"shouldResumeTrack: Auto-resume disabled (never resume)");
|
||||
return false;
|
||||
} else if (autoResumeTrackTimeout == -1) {
|
||||
// Always resume.
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"shouldResumeTrack: Auto-resume forced (always resume)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the last modified time is within the acceptable range.
|
||||
long lastModified =
|
||||
track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"shouldResumeTrack: lastModified = " + lastModified
|
||||
+ ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
|
||||
return lastModified > 0 && System.currentTimeMillis() - lastModified <=
|
||||
autoResumeTrackTimeout * 60 * 1000;
|
||||
}
|
||||
|
||||
public boolean isRecording() {
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
public long insertWaypointMarker(Waypoint waypoint) {
|
||||
if (!isRecording()) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to insert waypoint marker while not recording!");
|
||||
}
|
||||
|
||||
if (waypoint.getLocation() != null) {
|
||||
waypoint.setLength(length);
|
||||
waypoint.setDuration(waypoint.getLocation().getTime()
|
||||
@@ -731,6 +868,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
public long insertStatisticsMarker(Location location) {
|
||||
if (!isRecording()) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to insert statistics marker while not recording!");
|
||||
}
|
||||
|
||||
StringUtils utils = new StringUtils(TrackRecordingService.this);
|
||||
|
||||
// Create a new waypoint to save
|
||||
@@ -758,132 +900,175 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||
|
||||
// Create a new stats keeper for the next marker
|
||||
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||
waypointStatsBuilder.resumeAt(time);
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(time);
|
||||
updateCurrentWaypoint();
|
||||
return Long.parseLong(uri.getLastPathSegment());
|
||||
}
|
||||
|
||||
|
||||
private ServiceBinder binder = new ServiceBinder(this);
|
||||
|
||||
/**
|
||||
* The ITrackRecordingService is defined through IDL.
|
||||
* TODO: There is a bug in Android that leaks Binder instances. This bug is
|
||||
* especially visible if we have a non-static class, as there is no way to
|
||||
* nullify reference to the outer class (the service).
|
||||
* A workaround is to use a static class and explicitly clear service
|
||||
* and detach it from the underlying Binder. With this approach, we minimize
|
||||
* the leak to 24 bytes per each service instance.
|
||||
*
|
||||
* For more details, see the following bug:
|
||||
* http://code.google.com/p/android/issues/detail?id=6426.
|
||||
*/
|
||||
private final ITrackRecordingService.Stub binder =
|
||||
new ITrackRecordingService.Stub() {
|
||||
@Override
|
||||
public boolean isRecording() {
|
||||
return isRecording;
|
||||
}
|
||||
private static class ServiceBinder extends ITrackRecordingService.Stub {
|
||||
private TrackRecordingService service;
|
||||
|
||||
public ServiceBinder(TrackRecordingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the reference to the outer class to minimize the leak.
|
||||
*/
|
||||
public void detachFromService() {
|
||||
this.service = null;
|
||||
attachInterface(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecording() {
|
||||
return service.isRecording();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRecordingTrackId() {
|
||||
return recordingTrackId;
|
||||
}
|
||||
@Override
|
||||
public long getRecordingTrackId() {
|
||||
return service.recordingTrackId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRecorded() {
|
||||
return providerUtils.getLastTrackId() >= 0;
|
||||
}
|
||||
@Override
|
||||
public boolean hasRecorded() {
|
||||
return service.providerUtils.getLastTrackId() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long startNewTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
|
||||
Track track = new Track();
|
||||
TripStatistics trackStats = track.getStatistics();
|
||||
track.setName("new");
|
||||
long startTime = System.currentTimeMillis();
|
||||
trackStats.setStartTime(startTime);
|
||||
track.setStartId(-1);
|
||||
Uri trackUri = providerUtils.insertTrack(track);
|
||||
long trackId = Long.parseLong(trackUri.getLastPathSegment());
|
||||
track.setId(trackId);
|
||||
track.setName(String.format(getString(R.string.new_track), trackId));
|
||||
providerUtils.updateTrack(track);
|
||||
recordingTrackId = trackId;
|
||||
currentWaypointId = insertStatisticsMarker(null);
|
||||
isRecording = true;
|
||||
isMoving = true;
|
||||
statsBuilder = new TripStatisticsBuilder();
|
||||
statsBuilder.resumeAt(startTime);
|
||||
if (announcementFrequency != -1 && executer != null) {
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
length = 0;
|
||||
showNotification();
|
||||
registerLocationListener();
|
||||
splitManager.restore();
|
||||
signalManager.restore();
|
||||
return trackId;
|
||||
}
|
||||
@Override
|
||||
public long startNewTrack() {
|
||||
return service.startNewTrack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the given waypoint marker. Users can insert waypoint markers
|
||||
* to tag locations with a name, description, category etc.
|
||||
*
|
||||
* @param waypoint a waypoint
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertWaypointMarker(Waypoint waypoint) {
|
||||
return TrackRecordingService.this.insertWaypointMarker(waypoint);
|
||||
}
|
||||
/**
|
||||
* Insert the given waypoint marker. Users can insert waypoint markers
|
||||
* to tag locations with a name, description, category etc.
|
||||
*
|
||||
* @param waypoint a waypoint
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertWaypointMarker(Waypoint waypoint) {
|
||||
return service.insertWaypointMarker(waypoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a statistics marker. A statistics marker holds the stats for
|
||||
* the last segment up to this marker.
|
||||
*
|
||||
* @param location the location where to insert
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertStatisticsMarker(Location location) {
|
||||
return TrackRecordingService.this.insertStatisticsMarker(location);
|
||||
}
|
||||
/**
|
||||
* Insert a statistics marker. A statistics marker holds the stats for
|
||||
* the last segment up to this marker.
|
||||
*
|
||||
* @param location the location where to insert
|
||||
* @return the unique id of the inserted marker
|
||||
*/
|
||||
@Override
|
||||
public long insertStatisticsMarker(Location location) {
|
||||
return service.insertStatisticsMarker(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endCurrentTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
|
||||
isRecording = false;
|
||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||
if (recordingTrack != null) {
|
||||
TripStatistics stats = recordingTrack.getStatistics();
|
||||
stats.setStopTime(System.currentTimeMillis());
|
||||
stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
|
||||
long lastRecordedLocationId =
|
||||
providerUtils.getLastLocationId(recordingTrackId);
|
||||
ContentValues values = new ContentValues();
|
||||
if (lastRecordedLocationId >= 0
|
||||
&& recordingTrack.getStopId() >= 0) {
|
||||
values.put(TracksColumns.STOPID, lastRecordedLocationId);
|
||||
}
|
||||
values.put(TracksColumns.STOPTIME, stats.getStopTime());
|
||||
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
|
||||
getContentResolver().update(TracksColumns.CONTENT_URI, values,
|
||||
"_id=" + recordingTrack.getId(), null);
|
||||
}
|
||||
showNotification();
|
||||
recordingTrackId = -1;
|
||||
}
|
||||
@Override
|
||||
public void endCurrentTrack() {
|
||||
service.endCurrentTrack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAllTracks() {
|
||||
endCurrentTrack();
|
||||
providerUtils.deleteAllTracks();
|
||||
}
|
||||
@Override
|
||||
public void deleteAllTracks() {
|
||||
if (isRecording()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot delete all tracks while recording!");
|
||||
}
|
||||
service.providerUtils.deleteAllTracks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordLocation(Location loc) {
|
||||
onLocationChanged(loc);
|
||||
}
|
||||
@Override
|
||||
public void recordLocation(Location loc) {
|
||||
service.onLocationChanged(loc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sharedPreferenceChanged(String key) {
|
||||
Log.d(MyTracksConstants.TAG,
|
||||
"TrackRecordingService.sharedPrefereneChange");
|
||||
onSharedPreferenceChanged(key);
|
||||
}
|
||||
};
|
||||
public long startNewTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
|
||||
if (recordingTrackId != -1 || isRecording) {
|
||||
throw new IllegalStateException("A track is already in progress!");
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
acquireWakeLock();
|
||||
|
||||
TripStatistics getTripStatistics() {
|
||||
Track track = new Track();
|
||||
TripStatistics trackStats = track.getStatistics();
|
||||
trackStats.setStartTime(startTime);
|
||||
track.setStartId(-1);
|
||||
Uri trackUri = providerUtils.insertTrack(track);
|
||||
recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
|
||||
track.setId(recordingTrackId);
|
||||
track.setName(new DefaultTrackNameFactory(this).newTrackName(
|
||||
recordingTrackId, startTime));
|
||||
isRecording = true;
|
||||
isMoving = true;
|
||||
|
||||
providerUtils.updateTrack(track);
|
||||
statsBuilder = new TripStatisticsBuilder(startTime);
|
||||
waypointStatsBuilder = new TripStatisticsBuilder(startTime);
|
||||
currentWaypointId = insertStatisticsMarker(null);
|
||||
setUpAnnouncer();
|
||||
length = 0;
|
||||
showNotification();
|
||||
registerLocationListener();
|
||||
splitManager.restore();
|
||||
signalManager.restore();
|
||||
|
||||
// Reset the number of auto-resume retries.
|
||||
setAutoResumeTrackRetries(
|
||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0), 0);
|
||||
// Persist the current recording track.
|
||||
prefManager.setRecordingTrack(recordingTrackId);
|
||||
|
||||
return recordingTrackId;
|
||||
}
|
||||
|
||||
private void endCurrentTrack() {
|
||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
|
||||
if (recordingTrackId == -1 || !isRecording) {
|
||||
throw new IllegalStateException("No recording track in progress!");
|
||||
}
|
||||
|
||||
shutdownAnnouncer();
|
||||
isRecording = false;
|
||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||
if (recordingTrack != null) {
|
||||
TripStatistics stats = recordingTrack.getStatistics();
|
||||
stats.setStopTime(System.currentTimeMillis());
|
||||
stats.setTotalTime(stats.getStopTime() - stats.getStartTime());
|
||||
long lastRecordedLocationId =
|
||||
providerUtils.getLastLocationId(recordingTrackId);
|
||||
ContentValues values = new ContentValues();
|
||||
if (lastRecordedLocationId >= 0
|
||||
&& recordingTrack.getStopId() >= 0) {
|
||||
values.put(TracksColumns.STOPID, lastRecordedLocationId);
|
||||
}
|
||||
values.put(TracksColumns.STOPTIME, stats.getStopTime());
|
||||
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
|
||||
getContentResolver().update(TracksColumns.CONTENT_URI, values,
|
||||
"_id=" + recordingTrack.getId(), null);
|
||||
}
|
||||
showNotification();
|
||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
||||
releaseWakeLock();
|
||||
}
|
||||
|
||||
public TripStatistics getTripStatistics() {
|
||||
return statsBuilder.getStatistics();
|
||||
}
|
||||
|
||||
@@ -905,20 +1090,10 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
|
||||
public void setAnnouncementFrequency(int announcementFrequency) {
|
||||
this.announcementFrequency = announcementFrequency;
|
||||
if (mTTSAvailable) {
|
||||
if (announcementFrequency == -1) {
|
||||
if (executer != null) {
|
||||
executer.shutdown();
|
||||
executer = null;
|
||||
}
|
||||
} else {
|
||||
if (executer == null) {
|
||||
SafeStatusAnnouncerTask announcer =
|
||||
new SafeStatusAnnouncerTask(this);
|
||||
executer = new PeriodicTaskExecuter(announcer, this);
|
||||
}
|
||||
executer.scheduleTask(announcementFrequency * 60000);
|
||||
}
|
||||
if (announcementFrequency == -1) {
|
||||
shutdownAnnouncer();
|
||||
} else {
|
||||
setUpAnnouncer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,6 +1129,14 @@ public class TrackRecordingService extends Service implements LocationListener {
|
||||
LocationListenerPolicy locationListenerPolicy) {
|
||||
this.locationListenerPolicy = locationListenerPolicy;
|
||||
}
|
||||
|
||||
public int getAutoResumeTrackTimeout() {
|
||||
return autoResumeTrackTimeout;
|
||||
}
|
||||
|
||||
public void setAutoResumeTrackTimeout(int autoResumeTrackTimeout) {
|
||||
this.autoResumeTrackTimeout = autoResumeTrackTimeout;
|
||||
}
|
||||
|
||||
public SplitManager getSplitManager() {
|
||||
return splitManager;
|
||||
|
||||
@@ -103,4 +103,9 @@ public class ExtremityMonitor {
|
||||
public void setMax(double max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Min: " + min + " Max: " + max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,13 +36,13 @@ public class TripStatisticsBuilder {
|
||||
/**
|
||||
* The last location that the gps reported.
|
||||
*/
|
||||
private Location lastLocation = null;
|
||||
private Location lastLocation;
|
||||
|
||||
/**
|
||||
* The last location that contributed to the stats. It is also the last
|
||||
* location the user was found to be moving.
|
||||
*/
|
||||
private Location lastMovingLocation = null;
|
||||
private Location lastMovingLocation;
|
||||
|
||||
/**
|
||||
* The current speed in meters/second as reported by the gps.
|
||||
@@ -90,10 +90,13 @@ public class TripStatisticsBuilder {
|
||||
private long totalLocations = 0;
|
||||
|
||||
/**
|
||||
* Creates a new trip starting at the current system time.
|
||||
* Creates a new trip starting at the given time.
|
||||
*
|
||||
* @param startTime the start time.
|
||||
*/
|
||||
public TripStatisticsBuilder() {
|
||||
public TripStatisticsBuilder(long startTime) {
|
||||
data = new TripStatistics();
|
||||
resumeAt(startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,6 +106,9 @@ public class TripStatisticsBuilder {
|
||||
*/
|
||||
public TripStatisticsBuilder(TripStatistics statsData) {
|
||||
data = new TripStatistics(statsData);
|
||||
if (data.getStartTime() > 0) {
|
||||
resumeAt(data.getStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,8 +146,8 @@ public class TripStatisticsBuilder {
|
||||
|
||||
// Don't do anything if we didn't move since last fix:
|
||||
double distance = lastLocation.distanceTo(currentLocation);
|
||||
if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE
|
||||
&& currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
|
||||
if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE &&
|
||||
currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
|
||||
lastLocation = currentLocation;
|
||||
return false;
|
||||
}
|
||||
@@ -291,13 +297,6 @@ public class TripStatisticsBuilder {
|
||||
data.updateGradeExtremities(gradeBuffer.getAverage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the track at the current time.
|
||||
*/
|
||||
public void pause() {
|
||||
pauseAt(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the track at the given time.
|
||||
*
|
||||
@@ -312,13 +311,6 @@ public class TripStatisticsBuilder {
|
||||
paused = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the current track at the current time.
|
||||
*/
|
||||
public void resume() {
|
||||
resumeAt(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the current track at the given time.
|
||||
*
|
||||
@@ -364,8 +356,7 @@ public class TripStatisticsBuilder {
|
||||
}
|
||||
|
||||
public TripStatistics getStatistics() {
|
||||
// Take a snapshot - we do't want anyone messing with our internals
|
||||
// Take a snapshot - we don't want anyone messing with our internals
|
||||
return new TripStatistics(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Utility class for determining if newer-API features are available on the
|
||||
* current device.
|
||||
*
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
public class ApiFeatures {
|
||||
|
||||
/**
|
||||
* The API level of the Android version we're being run under.
|
||||
*/
|
||||
public static final int ANDROID_API_LEVEL = Integer.parseInt(
|
||||
Build.VERSION.SDK);
|
||||
|
||||
private static ApiFeatures instance;
|
||||
|
||||
/**
|
||||
* The API platform adapter supported by this system.
|
||||
*/
|
||||
private ApiPlatformAdapter apiPlatformAdapter;
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of this class.
|
||||
*/
|
||||
public static ApiFeatures getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ApiFeatures();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a specific singleton instance, to be used for unit tests.
|
||||
*/
|
||||
public static void injectInstance(ApiFeatures instance) {
|
||||
ApiFeatures.instance = instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow subclasses for mocking, but no direct instantiation.
|
||||
*/
|
||||
protected ApiFeatures() {
|
||||
if (getApiLevel() >= 5) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(
|
||||
"com.google.android.apps.mytracks.util.EclairPlatformAdapter");
|
||||
apiPlatformAdapter = (ApiPlatformAdapter) clazz.newInstance();
|
||||
} catch (Exception e) {
|
||||
Log.i(MyTracksConstants.TAG, "ApiFeatures: Unable to instantiate Eclair"
|
||||
+ " platform adapter", e);
|
||||
}
|
||||
}
|
||||
if (apiPlatformAdapter == null) {
|
||||
Log.i(MyTracksConstants.TAG,
|
||||
"ApiFeatures: Using default platform adapter");
|
||||
// Cupcake adapter is always supported, so it's safe to do static linkage.
|
||||
apiPlatformAdapter = new CupcakePlatformAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
public ApiPlatformAdapter getApiPlatformAdapter() {
|
||||
return apiPlatformAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
|
||||
*/
|
||||
public boolean hasBackup() {
|
||||
return getApiLevel() >= 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 hasModernSignalStrength() {
|
||||
return getApiLevel() >= 7;
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected int getApiLevel() {
|
||||
return ANDROID_API_LEVEL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
/**
|
||||
* A set of methods that may be implemented in a platform specific way.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public interface ApiPlatformAdapter {
|
||||
|
||||
/**
|
||||
* Puts the specified service into foreground.
|
||||
*
|
||||
* @param service the service to be put in foreground.
|
||||
* @param notificationManager the notification manager used to post the given
|
||||
* notification.
|
||||
* @param id the ID of the notification, unique within the application.
|
||||
* @param notification the notification to post.
|
||||
*/
|
||||
void startForeground(Service service, NotificationManager notificationManager,
|
||||
int id, Notification notification);
|
||||
|
||||
/**
|
||||
* Puts the given service into background.
|
||||
* @param service the service to put into background.
|
||||
* @param notificationManager the notification manager to user when removing
|
||||
* notifications.
|
||||
* @param id the ID of the notification to be remove, or -1 if the
|
||||
* notification shouldn't be removed.
|
||||
*/
|
||||
void stopForeground(Service service, NotificationManager notificationManager,
|
||||
int id);
|
||||
|
||||
/**
|
||||
* Terminates the given handler thread.
|
||||
*
|
||||
* @param handlerThread the thread to be terminated.
|
||||
* @return true whether the handler has been stopped or not.
|
||||
*/
|
||||
boolean stopHandlerThread(HandlerThread handlerThread);
|
||||
}
|
||||
@@ -32,12 +32,12 @@ public class ChartsExtendedEncoder {
|
||||
|
||||
public static String getEncodedValue(int scaled) {
|
||||
int index1 = scaled / CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||
|
||||
if (index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
if (index1 < 0 || index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
return MISSING_POINT_EXTENDED_ENCODING;
|
||||
}
|
||||
if (index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
|
||||
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||
if (index2 < 0 || index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||
return MISSING_POINT_EXTENDED_ENCODING;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
/**
|
||||
* The Cupcake (API level 3) specific implementation of the
|
||||
* {@link ApiPlatformAdapter}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class CupcakePlatformAdapter implements ApiPlatformAdapter {
|
||||
|
||||
@Override
|
||||
public void startForeground(Service service,
|
||||
NotificationManager notificationManager, int id,
|
||||
Notification notification) {
|
||||
service.setForeground(true);
|
||||
notificationManager.notify(id, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopForeground(Service service,
|
||||
NotificationManager notificationManager, int id) {
|
||||
service.setForeground(false);
|
||||
if (id != -1) {
|
||||
notificationManager.cancel(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
||||
// Do nothing, as Cupcake doesn't provide quit().
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
/**
|
||||
* The Eclair (API level 5) specific implementation of the
|
||||
* {@link ApiPlatformAdapter}.
|
||||
*
|
||||
* @author Bartlomiej Niechwiej
|
||||
*/
|
||||
public class EclairPlatformAdapter implements ApiPlatformAdapter {
|
||||
|
||||
@Override
|
||||
public void startForeground(Service service,
|
||||
NotificationManager notificationManager, int id,
|
||||
Notification notification) {
|
||||
service.startForeground(id, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopForeground(Service service,
|
||||
NotificationManager notificationManager, int id) {
|
||||
service.stopForeground(id != -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
||||
return handlerThread.quit();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class FileUtils {
|
||||
* A set of characters that are prohibited from being in file names.
|
||||
*/
|
||||
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
||||
Pattern.compile("[^ A-Za-z0-9_.()]+");
|
||||
Pattern.compile("[^ A-Za-z0-9_.()-]+");
|
||||
|
||||
/**
|
||||
* Builds a path inside the My Tracks directory in the SD card.
|
||||
@@ -69,8 +69,6 @@ public class FileUtils {
|
||||
* Normalizes the input string and make sure it is a valid fat32 file name.
|
||||
*
|
||||
* @param name the name to normalize
|
||||
* @param overheadSize the number of additional characters that will be added
|
||||
* to the name after sanitization
|
||||
* @return the sanitized name
|
||||
*/
|
||||
String sanitizeName(String name) {
|
||||
|
||||
@@ -233,11 +233,10 @@ public class SpreadsheetsClient extends GDataServiceClient {
|
||||
throws ParseException, IOException {
|
||||
switch (cause.getStatusCode()) {
|
||||
case HttpException.SC_CONFLICT:
|
||||
Entry entry = null;
|
||||
if (entryClass != null) {
|
||||
InputStream is = cause.getResponseStream();
|
||||
if (is != null) {
|
||||
entry = parseEntry(entryClass, cause.getResponseStream());
|
||||
parseEntry(entryClass, cause.getResponseStream());
|
||||
}
|
||||
}
|
||||
throw new IOException(message);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<factorypath>
|
||||
<factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="WKSPJAR" id="/MyTracksTest/libs/AndroidMockGenerator.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="ANDROID_SDK/platforms/android-8/android.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="ANDROID_SDK/add-ons/addon_google_apis_google_inc_8/libs/maps.jar" enabled="true" runInBatchMode="false"/>
|
||||
<factorypathentry kind="VARJAR" id="MYTRACKS_BIN" enabled="true" runInBatchMode="false"/>
|
||||
</factorypath>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#Thu Aug 12 19:30:48 BRT 2010
|
||||
#Mon Nov 01 02:42:03 BRST 2010
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.apt.aptEnabled=true
|
||||
org.eclipse.jdt.apt.genSrcDir=.apt_generated
|
||||
org.eclipse.jdt.apt.processorOptions/bin_dir=MyTracksTest/bin
|
||||
org.eclipse.jdt.apt.processorOptions/logfile=MyTracksTest/annotations.log
|
||||
org.eclipse.jdt.apt.processorOptions/bin_dir=%PROJECT.DIR%/bin
|
||||
org.eclipse.jdt.apt.processorOptions/logfile=%PROJECT.DIR%/annotations.log
|
||||
org.eclipse.jdt.apt.reconcileEnabled=false
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import com.google.android.maps.mytracks.R;
|
||||
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
/**
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class ChartValueSeriesTest extends AndroidTestCase {
|
||||
private ChartValueSeries series;
|
||||
private Paint fillPaint1;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
fillPaint1 = new Paint();
|
||||
fillPaint1.setStyle(Style.FILL);
|
||||
fillPaint1.setColor(getContext().getResources().getColor(R.color.green));
|
||||
fillPaint1.setAntiAlias(true);
|
||||
series = new ChartValueSeries(getContext(),
|
||||
"###,###",
|
||||
fillPaint1,
|
||||
null,
|
||||
100,
|
||||
R.string.elevation);
|
||||
}
|
||||
|
||||
public void testInitialConditions() {
|
||||
assertEquals(25, series.getInterval());
|
||||
assertEquals(1, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(0, series.getMax());
|
||||
assertEquals(0.0, series.getSpread());
|
||||
assertEquals(fillPaint1, series.getPaint());
|
||||
assertEquals(getContext().getString(R.string.elevation),
|
||||
series.getTitle());
|
||||
assertTrue(series.isEnabled());
|
||||
}
|
||||
|
||||
public void testEnabled() {
|
||||
series.setEnabled(false);
|
||||
assertFalse(series.isEnabled());
|
||||
}
|
||||
|
||||
public void testSmallUpdates() {
|
||||
series.update(0);
|
||||
series.update(10);
|
||||
series.updateDimension();
|
||||
assertEquals(25, series.getInterval());
|
||||
assertEquals(3, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(100, series.getMax());
|
||||
assertEquals(100.0, series.getSpread());
|
||||
}
|
||||
|
||||
public void testBigUpdates() {
|
||||
series.update(0);
|
||||
series.update(901);
|
||||
series.updateDimension();
|
||||
assertEquals(200, series.getInterval());
|
||||
assertEquals(5, series.getMaxLabelLength());
|
||||
assertEquals(0, series.getMin());
|
||||
assertEquals(1000, series.getMax());
|
||||
assertEquals(1000.0, series.getSpread());
|
||||
}
|
||||
|
||||
public void testNotZeroBasedUpdates() {
|
||||
series.update(500);
|
||||
series.update(1401);
|
||||
series.updateDimension();
|
||||
assertEquals(200, series.getInterval());
|
||||
assertEquals(5, series.getMaxLabelLength());
|
||||
assertEquals(500, series.getMin());
|
||||
assertEquals(1500, series.getMax());
|
||||
assertEquals(1000.0, series.getSpread());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.google.android.apps.mytracks;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
/**
|
||||
* @author Sandor Dornbush
|
||||
*/
|
||||
public class DialogManagerTest
|
||||
extends ActivityInstrumentationTestCase2<MyTracks> {
|
||||
|
||||
public DialogManagerTest() {
|
||||
super(MyTracks.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MyTracks.clearInstance();
|
||||
assertNull(MyTracks.getInstance());
|
||||
}
|
||||
|
||||
public void test_onCreateChartSettings() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(DialogManager.DIALOG_CHART_SETTINGS, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof ChartSettingsDialog);
|
||||
}
|
||||
|
||||
public void test_onCreateImportProgress() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(
|
||||
DialogManager.DIALOG_IMPORT_PROGRESS, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof ProgressDialog);
|
||||
}
|
||||
|
||||
public void test_onCreateProgress() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(DialogManager.DIALOG_PROGRESS, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof ProgressDialog);
|
||||
ProgressDialog pd = (ProgressDialog) d;
|
||||
assertEquals(100, pd.getMax());
|
||||
assertEquals(10, pd.getProgress());
|
||||
}
|
||||
|
||||
public void test_onCreateSendToGoogle() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(DialogManager.DIALOG_SEND_TO_GOOGLE, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof SendToGoogleDialog);
|
||||
}
|
||||
|
||||
public void test_onCreateSendToGoogleResult() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(
|
||||
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof AlertDialog);
|
||||
}
|
||||
|
||||
public void test_onCreateWriteProgress() {
|
||||
Dialog d =
|
||||
getActivity().onCreateDialog(
|
||||
DialogManager.DIALOG_WRITE_PROGRESS, null);
|
||||
assertNotNull(d);
|
||||
assertTrue(d instanceof ProgressDialog);
|
||||
}
|
||||
|
||||
}
|
||||