Compare commits

..

76 Commits

Author SHA1 Message Date
Rodrigo Damazio e35e8bcdce Updating version number to 1.0.16 in preparation for release. 2010-08-30 18:42:48 -03:00
Rodrigo Damazio 5f953aa47f Fixing version display (missed merge). 2010-08-30 18:36:16 -03:00
Sandor Dornbush 3b3641c69e Merging changes from head. 2010-08-27 17:21:33 -07:00
Rodrigo Damazio 56ccd518d6 Adding missing Eclipse configuration files for developers - these have to be manually imported 2010-08-24 22:48:49 -03:00
Rodrigo Damazio 0e119950c2 Merging Sandor's changes 2010-08-24 17:02:36 -03:00
Sandor Dornbush 4be4467fc6 Fix NPE. 2010-08-24 12:20:18 -07:00
Sandor Dornbush 06636b2a28 Fix test access modifier. 2010-08-23 09:12:12 -07:00
Sandor Dornbush 71a36d1b25 Address Rodrigo's comments. 2010-08-23 09:05:55 -07:00
Sandor Dornbush 9ba93ff8d2 Fix javadoc. 2010-08-23 08:03:54 -07:00
Sandor Dornbush 4059308dd7 Added comments and license to pref mgr.
Removed sampling from track overlay.
2010-08-22 12:51:40 -07:00
sandordornbush 9f43dd2db5 Reverting classpath changes. 2010-08-20 16:11:36 -07:00
sandordornbush 28d868985e One more minor bug fix for a NPE. 2010-08-20 13:32:53 -07:00
sandordornbush cf1be26da2 Two minor bug fixes from the developer console. 2010-08-20 10:51:09 -07:00
Sandor Dornbush 1de9b73aa0 Address Rodrigo's comments.
Add service preference manager.
2010-08-18 22:05:01 -07:00
Sandor Dornbush beddc8e1ea Add Track Writer test.
Fix total time bug in stats activity.
Fix track segments on the map overlay.
2010-08-16 17:16:43 -07:00
Rodrigo Damazio e0c34d20a8 Removing DO NOT TRANSLATE values from Japanese translation 2010-08-16 00:57:03 -03:00
Rodrigo Damazio cc6db5b5cc Merging japanese translation 2010-08-16 00:54:44 -03:00
Rodrigo Damazio c38df03add Ignoring eclipse workspace metadata if present 2010-08-13 13:03:58 -03:00
Rodrigo Damazio 63402358d1 Fixing empty method comment 2010-08-13 04:53:07 -03:00
Rodrigo Damazio 3ae1de735e Removing overlooked debug lines 2010-08-13 04:52:14 -03:00
Rodrigo Damazio a701eb0e4f Fixing 24-hour format mistake 2010-08-13 04:37:37 -03:00
Rodrigo Damazio 09caf017a9 Adding support for segments 2010-08-13 04:37:14 -03:00
Rodrigo Damazio 32b8b2093d REAL performance improvement in importer - using a bulk insertion 2010-08-13 03:50:55 -03:00
Rodrigo Damazio c84a4f1aa8 Moving androidmock to the right directory and making eclipse use it properly 2010-08-12 22:16:38 -03:00
Rodrigo Damazio d90c0b5431 Optimizing the importer a little 2010-08-12 22:14:47 -03:00
Rodrigo Damazio 94f6785f0d Removing no longer used provider utility methods 2010-08-12 22:06:58 -03:00
Rodrigo Damazio f01c382807 Style fixing SAX importer contribution 2010-08-12 20:22:54 -03:00
Rodrigo Damazio 6d42803dd8 Updating statistics object usage in SAX importer 2010-08-12 19:43:27 -03:00
Rodrigo Damazio 61e908ccb5 Merge SAX GPX importer contribution 2010-08-12 18:37:38 -03:00
Sandor Dornbush 7bae8c34a7 Integrate most of Rodrigo's comments. 2010-08-11 22:02:49 -07:00
Sandor Dornbush 3960341640 Revert classpath 2010-08-09 16:45:36 -07:00
Sandor Dornbush 06e453dd05 Merging changes from realtime branch into mainline. 2010-08-07 10:38:41 -07:00
boiledorange73 361b1cbad4 Reverted "DO NOT TRANSLATE" entries 2010-08-04 19:51:00 +09:00
boiledorange73 56a866fca7 Modified bits of values-ja files. 2010-08-04 17:13:35 +09:00
boiledorange73 49e0d68374 res/values-ja added 2010-08-04 14:35:30 +09:00
Sandor Dornbush c9ffd0b4bb Exporting the TrackRecordingService. 2010-07-27 16:34:13 -07:00
Steffen Horlacher 6fd4294391 Bugfix for empty total time in trip statistics and missing first location in exported files 2010-06-06 23:31:03 +02:00
Rodrigo Damazio 88b2b85f2a Making Track and Waypoint use the TripStatisticsData class.
This grealy simplifies those classes since the fields are the same.
There's certainly more cleanup to do on top of this, but I thought this
was already more than enough for a single change.

The only *functional* change expected from this is that the average
speed is now calculated over the time until the last known position,
which IMHO makes sense since the distance used in that calculation only
includes up to that position. The visible effect is that having no GPS
signal doesn't make the speed decrease.

This was imported from perforce CL 15953263.
2010-06-04 14:59:09 -03:00
Rodrigo Damazio 2c1dff6ac8 Adding test project files (TODO: replace easymock with android mock) 2010-06-02 19:26:31 -03:00
Rodrigo Damazio 481b42f563 Auto-merge 2010-06-02 16:58:49 -03:00
Rodrigo Damazio 597c321009 Added tag v1.0.15 for changeset e774d13e5b6a 2010-06-02 16:57:46 -03:00
Rodrigo Damazio 76651f75d8 Automated merge with https://tnapierala-mytracks.googlecode.com/hg/ 2010-06-02 16:22:24 -03:00
t napierala 3d8bae55a1 pl_PL - removed some non-translatable strings 2010-06-01 10:00:28 +02:00
Steffen Horlacher 4b6869e367 Added check for negative time changes and bugfix for files with multiple tracks 2010-05-30 20:06:22 +02:00
Steffen Horlacher 477ba7c081 Changes according to comments 2010-05-30 18:10:56 +02:00
johan helin 5b6d9725b8 Removed all strings that should not be translated in the swedish translation of arrays.xml 2010-05-30 14:01:26 +02:00
t napierala 25c3cea115 pl_PL removed colors.xml, removed non-translatable strings as per Issue 68, comment 2 2010-05-30 13:20:08 +02:00
johan helin d90601276f Finnished translating my tracks to swedish 2010-05-29 13:30:20 +02:00
johan helin 4f692bc698 Finnished translating strings.xml and did some more of the strings in arrays.xml 2010-05-29 12:44:30 +02:00
johan helin d8dd02eef0 Further strings translated 2010-05-28 22:38:04 +02:00
johan helin 8ad65ae233 First commit with cleanup from english and some strings translated 2010-05-28 22:22:39 +02:00
t napierala eabbb4569b final translation 2010-05-26 16:34:26 +02:00
t napierala 49888b771d first part of changes 2010-05-26 14:29:40 +02:00
ob 2b1c1cd052 Add a missing string for "Elevation" to the French strings.xml file. 2010-05-24 18:23:39 +01:00
Steffen Horlacher 61884e67ec track rollback: javadoc and some minor other corrections 2010-05-23 12:58:50 +02:00
Steffen Horlacher 9a2f028843 code format 2010-05-23 11:26:41 +02:00
Steffen Horlacher 2b56acf164 code format 2010-05-23 11:21:27 +02:00
Steffen 98b67dcc28 Sax style GPX file importer and tests 2010-05-23 11:08:51 +02:00
Rodrigo Damazio 500dfa3107 Releasing mytracks 1.0.15 2010-05-23 00:00:00 -03:00
Rodrigo Damazio c4efcfd2db Fixing bug in GPX import - we were reading the last point time too soon 2010-05-22 01:17:59 -07:00
Rodrigo Damazio 81ee6bbcf6 Add a few calls back into SendToMyMaps which actually add the stats to the ending waypoint. (imported from perforce) 2010-05-22 00:41:40 -07:00
Rodrigo Damazio e4e2e76e06 Added tag v1.0.14 for changeset 4c02a4515632 2010-05-20 16:53:43 -07:00
Rodrigo Damazio f847862e84 Releasing 1.0.14 (no code changes, just a release mistake) 2010-05-20 16:53:40 -07:00
Rodrigo Damazio 427dbeafde Added tag v1.0.13 for changeset 84cce31f6b83 2010-05-20 12:18:35 -07:00
Rodrigo Damazio 99ac85b40e Releasing 1.0.13 2010-05-20 12:18:29 -07:00
Rodrigo Damazio 020388baed Fixing uploading bug which happened in gzipped responses 2010-05-20 11:21:46 -07:00
Rodrigo Damazio 4ade79de4f Added tag v1.0.12 for changeset 51aa4e7cf7d8 2010-05-20 05:14:00 -03:00
Rodrigo Damazio 870dda9b8b Importing tests source 2010-05-19 17:35:08 -07:00
Rodrigo Damazio 27dd5298f7 Importing all the source code :) 2010-05-19 17:32:31 -07:00
Rodrigo Damazio 6d5d406ce9 Importing external libraries 2010-05-19 17:31:41 -07:00
Rodrigo Damazio ffb5e7e6f7 Importing project files 2010-05-19 17:31:20 -07:00
Rodrigo Damazio 0de101c89f Updating resources to the latest version 2010-05-19 17:30:26 -07:00
Rodrigo Damazio bd3a0766e1 Adding service definition 2010-05-07 17:58:54 -03:00
Rodrigo Damazio f536cc8f8a Adding hgignore for bin and gen dirs 2010-05-07 15:59:52 -03:00
Rodrigo Damazio 826c0495fc Initial import of MyTracks content files and their dependencies.
MyTracksUtils and MyTracksConstants are cut-down versions of the original files
which contain only what's used by the content package, we'll later post the rest
of them.
2010-05-07 15:59:33 -03:00
Rodrigo Damazio 9eac541ce5 Initial import of MyTracks resources and assets 2010-05-07 15:57:42 -03:00
139 changed files with 3000 additions and 9936 deletions
-1
View File
@@ -3,4 +3,3 @@ syntax:glob
*/bin
*/gen/**
.metadata
MyTracksTest/annotations.log
+2 -2
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<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 @@
#Wed Sep 29 18:44:16 BRT 2010
#Thu Aug 12 18:54:52 BRT 2010
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+15 -40
View File
@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.maps.mytracks"
android:installLocation="auto"
android:versionCode="22"
android:versionName="1.0.21">
android:versionCode="17"
android:versionName="1.0.16">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
android:label="@string/app_name">
<uses-library android:name="com.google.android.maps" />
<meta-data android:name="com.google.android.backup.api_key"
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"
android:label="My Tracks"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale">
@@ -39,13 +36,16 @@
<activity android:name="com.google.android.apps.mymaps.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale" />
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale">
</activity>
<activity android:name="com.google.android.apps.mytracks.MyTracksList"
android:label="Recorded Tracks" />
android:label="Recorded Tracks">
</activity>
<activity android:name="com.google.android.apps.mytracks.MyTracksWaypointsList"
android:label="Recorded Waypoints" />
android:label="Recorded Waypoints">
</activity>
<activity android:name="com.google.android.apps.mytracks.MyTracksDetails"
android:label="@string/trackdetails_title" />
@@ -56,34 +56,24 @@
<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"/>
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
android:label="@string/welcome"/>
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" />
@@ -100,22 +90,6 @@
<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"
@@ -123,3 +97,4 @@
android:smallScreens="true"
android:anyDensity="true" />
</manifest>
-2
View File
@@ -9,11 +9,9 @@
<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"/>
@@ -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/left_arrow_stats" />
android:drawable="@drawable/btn_arrow_left_default" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_stats_press" />
android:drawable="@drawable/btn_arrow_left_press" />
<item android:drawable="@drawable/left_arrow_stats" />
<item android:drawable="@drawable/btn_arrow_left_default" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.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/left_arrow_track" />
android:drawable="@drawable/btn_arrow_right_default" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_track_press" />
android:drawable="@drawable/btn_arrow_right_press" />
<item android:drawable="@drawable/left_arrow_track" />
<item android:drawable="@drawable/btn_arrow_right_default" />
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/left_arrow_chart" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_chart_press" />
<item android:drawable="@drawable/left_arrow_chart" />
</selector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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>
+1 -6
View File
@@ -20,15 +20,10 @@
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="1003"
android:orderInCategory="1002"
android:title="@string/help"/>
</menu>
+1
View File
@@ -155,6 +155,7 @@ 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>
+1
View File
@@ -158,6 +158,7 @@ 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>
+1
View File
@@ -158,6 +158,7 @@ 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>
+1
View File
@@ -164,6 +164,7 @@ 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>
+1 -1
View File
@@ -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>
-9
View File
@@ -146,15 +146,6 @@ 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">
+6 -40
View File
@@ -29,7 +29,6 @@ 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>
@@ -76,9 +75,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 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>
<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>
<!-- Text and links when uploading to Google My Maps -->
<string name="new_map_description">Utworzono za pomocą My Tracks na Android.</string>
@@ -165,7 +164,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>
@@ -193,45 +192,14 @@ 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 będzie również przesyłana</string>
<string name="settings_share_url_only_summary_off">Zawartość trasy również będzie 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>
@@ -383,10 +351,8 @@ 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 na karcie SD.</string>
<string name="io_read_finished">Zakończono odczytywanie z karty SD.</string>
<string name="io_write_finished">Zakończono zapisywanie trasy na karcie SD.</string>
<!-- VersionChecker -->
<string name="version_check_title">Sprawdzanie aktualizacji</string>
+1
View File
@@ -157,6 +157,7 @@ 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>
+44 -44
View File
@@ -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 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_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_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 position tillgänglig.</string>
<string name="error_unable_to_insert_marker">Kan inte skapa markör. Ingen possition tillgänglig.</string>
<!-- Status messages -->
<string name="wait_for_fix">Väntar på bättre GPS-position. Se till att himlen är synlig.</string>
<string name="wait_for_fix">Väntar på bättre GPS-posstion. 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 Mina Kartor har skapats på maps.google.com.</string>
<string name="status_new_mymap_has_been_created">En ny Google My Map 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 skickats till ett nytt kalkylblad på docs.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_docs">Ditt spår har skickats till docs.google.com.</string>
<string name="status_mymap_info">Det kan ibland ta några minuter innan nya spår visas.</string>
<string name="status_enable_gps">Klicka här för att gå till Säkerhet och Platsinställningar och kryssa i \'aktivera gps-satteliter\'. Tryck på tillbakaknappen för att återgå.</string>
<string name="status_approximate_location">OBS: En ungefärlig position som avgörs från mobilmaster och WIFI-nät visas.</string>
<string name="status_approximate_location">OBS: En ungerfä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 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="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="import_progress_message">Importerar ett GPS-spår. Detta kan ta en stund.</string>
<string name="write_progress_message">Skriver till SD-kortet. Detta kan ta en stund.</string>
<string name="write_progress_message">Skriver til 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 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_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_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 Mina Kartor</string>
<string name="tracklist_btn_send_all_to_mymaps">Skicka till Google My Maps</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. Tryck &amp; håll för fler alternativ.</string>
<string name="tracklist_pick_one">Välj ett spår. TryckPress&amp;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 Mina Kartor</string>
<string name="tracklist_share_link">En länk till Google My Map</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. Tryck &amp; håll för fler alternativ.</string>
<string name="waypointslist_pick_one">Välj en markör. TryckPress&amp;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">Uppladdade kartor kommer inte att visas offentligt 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_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 delningsmarkör kommer infogas</string>
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delnginsmarkör kommer infogas</string>
<string name="pace_label">Tempo</string>
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS-satelliter</string>
<string name="mytracks_sky">GPS-satteliter</string>
<!-- My Maps -->
<string name="select_map_message">Välj en karta från ditt konto:</string>
<string name="select_map_message">Välje en karta från ditt konto:</string>
<string name="create_new_map">Skapa en ny karta...</string>
<string name="map_list_title">Skicka till Google Mina Kartor</string>
<string name="map_list_title">Skicka till Google My Maps</string>
<string name="maps_list_loading">Laddar kartor...</string>
<string name="maps_list_failed">Dina kartor kunde inte laddas just nu!</string>
<string name="maps_list_failed">Din 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änk</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_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 inga kartor än.</string>
<string name="mapslist_no_maps_available">Du har inge 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öjd</string>
<string name="elevation_label">Höjt</string>
<string name="latitude_label">Latitud</string>
<string name="longitude_label">Longitud</string>
<!-- Min -->
<string name="min_elevation_label">Minimihöjd</string>
<string name="min_grade_label">Minimilutning</string>
<string name="min_pace_label">Minimitempo</string>
<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>
<!-- 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 i timmen</string>
<string name="kilometer_per_hour_long">kilometer per timme</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 i timmen</string>
<string name="mile_per_hour_long">miles per timme</string>
<string name="min_per_mile">min/mi</string>
<string name="per_mile">per mile</string>
<!-- Stats Labels -->
<string name="moving_time_label">Rörlig tid</string>
<string name="moving_time_label">Rörelsetid</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 Mina Kartor</string>
<string name="sendtogoogle_google_docs">Google Dokument</string>
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
<string name="sendtogoogle_google_docs">Google Docs</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>
-286
View File
@@ -1,286 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Task frequency -->
<string-array name="task_frequency_options">
<item>关闭</item>
<item>1 分钟</item>
<item>2 分钟</item>
<item>5 分钟</item>
<item>10 分钟</item>
<item>15 分钟</item>
<item>30 分钟</item>
<item>60 分钟</item>
</string-array>
<!-- Minimum recording distance -->
<string-array name="min_recording_distance_options">
<item>1 米</item>
<item>2 米</item>
<item>3 米</item>
<item>4 米</item>
<item>5 米 (推荐)</item>
<item>10 米</item>
<item>100 米</item>
</string-array>
<string-array name="min_recording_distance_options_ft">
<item>3 英尺</item>
<item>6 英尺</item>
<item>10 英尺</item>
<item>13 英尺</item>
<item>16 英尺 (推荐)</item>
<item>33 英尺</item>
<item>328 英尺</item>
</string-array>
<!-- Maximum recording distance -->
<string-array name="max_recording_distance_options">
<item>50 米</item>
<item>100 米</item>
<item>200 米 (推荐)</item>
<item>500 米</item>
<item>1000 米</item>
<item>5000 米</item>
</string-array>
<string-array name="max_recording_distance_options_ft">
<item>164 英尺</item>
<item>328 英尺</item>
<item>656 英尺 (推荐)</item>
<item>1640 英尺</item>
<item>0.6 英里</item>
<item>3.1 英里</item>
</string-array>
<!-- Minimum recording interval -->
<string-array name="min_recording_interval_options">
<item>省电 (自适应)</item>
<item>高精度 (自适应)</item>
<item>最高 (推荐)</item>
<item>2 秒</item>
<item>3 秒</item>
<item>4 秒</item>
<item>5 秒</item>
<item>10 秒</item>
<item>20 秒</item>
<item>30 秒</item>
<item>1 分钟</item>
<item>2 分钟</item>
<item>5 分钟</item>
<item>10 分钟</item>
<item>15 分钟</item>
<item>30 分钟</item>
</string-array>
<!-- Minimum required accuracy -->
<string-array name="min_required_accuracy_options">
<item>10 米 (良好的 GPS 信号)</item>
<item>20 米</item>
<item>50 米</item>
<item>100 米</item>
<item>200 米 (推荐)</item>
<item>500 米</item>
<item>1000 米</item>
<item>2000 米</item>
<item>5000 米 (糟糕的 GPS 信号)</item>
</string-array>
<string-array name="min_required_accuracy_options_ft">
<item>33 英尺 (良好的 GPS 信号)</item>
<item>66 英尺</item>
<item>165 英尺</item>
<item>328 英尺</item>
<item>656 英尺 (推荐)</item>
<item>1640 英尺</item>
<item>0.6 英里</item>
<item>1.2 英里</item>
<item>3.1 英里 (糟糕的 GPS 信号)</item>
</string-array>
<!-- Split frequency -->
<string-array name="split_frequency_options">
<item>不分割</item>
<item>100 千米</item>
<item>50 千米</item>
<item>25 千米</item>
<item>10 千米</item>
<item>5 千米</item>
<item>1 千米</item>
<item>60 分钟</item>
<item>30 分钟</item>
<item>15 分钟</item>
<item>10 分钟</item>
<item>5 分钟</item>
<item>2 分钟</item>
<item>1 分钟</item>
</string-array>
<string-array name="split_frequency_options_ft">
<item>不分割</item>
<item>100 英里</item>
<item>50 英里</item>
<item>25 英里</item>
<item>10 英里</item>
<item>5 英里</item>
<item>1 英里</item>
<item>60 分钟</item>
<item>30 分钟</item>
<item>15 分钟</item>
<item>10 分钟</item>
<item>5 分钟</item>
<item>2 分钟</item>
<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>
<item>多种类型</item>
<item>其它</item>
<item>跑步</item>
<item>街头跑步</item>
<item>赛道跑步</item>
<item>户外跑步</item>
<item>行走</item>
<item>快步行走</item>
<item>徒步</item>
<item>沿路徒步</item>
<item>野外徒步</item>
<item>攀岩</item>
<item>骑行</item>
<item>道路骑行</item>
<item>山地骑行</item>
<item>赛道骑行</item>
<item>滑冰</item>
<item>轮滑</item>
<item>滑板</item>
<item>雪攀</item>
<item>滑雪</item>
<item>雪板</item>
<item>雪撬</item>
<item>浮冰</item>
<item>航海</item>
<item>划水</item>
<item>皮划艇</item>
<item>赛艇</item>
<item>摩托艇</item>
<item>摆渡</item>
<item>遥控船</item>
<item>冲浪</item>
<item>帆板</item>
<item>风筝冲浪</item>
<item>滑水</item>
<item>托板</item>
<item>飞机</item>
<item>水上飞机</item>
<item>商业飞机</item>
<item>直升机</item>
<item>热气球</item>
<item>软式飞艇</item>
<item>遥控飞机</item>
<item>遥控直升机</item>
<item>蹦极</item>
<item>滑翔</item>
<item>悬挂滑翔</item>
<item>滑翔伞</item>
<item>马术</item>
<item>骑马</item>
<item>骑驴</item>
<item>驾车</item>
<item>巴士</item>
<item>火车</item>
<item>摩托车</item>
<item>越野摩托</item>
<item>全地形车</item>
<item>陆帆</item>
<item>飞盘</item>
<item>极限飞盘</item>
</array>
<array name="waypoint_types">
<item>提款机</item>
<item>银行</item>
<item>酒吧</item>
<item>烧烤</item>
<item>建筑物</item>
<item>公交站</item>
<item>咖啡</item>
<item>营火</item>
<item>营地</item>
<item>教堂</item>
<item>咖啡店</item>
<item>便利店</item>
<item>饮用水</item>
<item>落石</item>
<item>渡口</item>
<item>明火点</item>
<item>消防站</item>
<item>钓鱼</item>
<item>加油站</item>
<item>高尔夫球场</item>
<item>杂货店</item>
<item>理发店</item>
<item>港口</item>
<item>徒步路线</item>
<item>宾馆</item>
<item>房屋</item>
<item>问讯处</item>
<item>交叉口</item>
<item>邮箱</item>
<item>超市</item>
<item>集合点</item>
<item>剧场</item>
<item>停车场</item>
<item>邮局</item>
<item>凹陷</item>
<item>警察局</item>
<item>铁路岔口</item>
<item>餐馆</item>
<item>购物</item>
<item>景点</item>
<item>商店</item>
<item>游泳池</item>
<item>电话</item>
<item>剧院</item>
<item>厕所</item>
<item>游客咨询点</item>
<item>隧道</item>
<item>树</item>
<item>瀑布</item>
<item>水泉</item>
<item>路点</item>
</array>
</resources>
-486
View File
@@ -1,486 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Used by the Menu -->
<string name="start_recording">记录轨迹</string>
<string name="stop_recording">停止记录</string>
<string name="switch_to_sat">卫星图模式</string>
<string name="switch_to_map">地图模式</string>
<string name="mylocation">我的位置</string>
<string name="list_tracks">轨迹列表</string>
<string name="list_markers">标记</string>
<string name="delete_all_tracks">删除所有轨迹</string>
<string name="settings">设置</string>
<string name="elevation">海拔</string>
<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>
<string name="error_no_gps_location_provider">此设备上没有 GPS 定位装置.</string>
<string name="error_sending_to_mymap">发送你的轨迹到 Google My Map 时发生错误. 请稍后重试.</string>
<string name="error_sending_to_docs">发送你的轨迹到 Google Docs 时发生错误. 请稍后重试.</string>
<string name="error_unable_to_start_recording">无法开始记录. 请重试.</string>
<string name="error_unable_to_stop_recording">无法停止记录. 请重试.</string>
<string name="error_unable_to_read_file">无法读取文件.</string>
<string name="error_invalid_gpx_format">文件不是有效的 GPX 格式.</string>
<string name="error_generic">发生错误. 请重试.</string>
<string name="error_track_does_not_exist">轨迹不存在.</string>
<string name="error_out_of_memory">文件太大 (必须小于 500 KB). 中止操作.</string>
<string name="error_unable_to_insert_marker">无法插入标记. 定位尚未完成.</string>
<!-- Status messages -->
<string name="wait_for_fix">等待更准确的 GPS 定位. 请确定可以看见天空.</string>
<string name="status_now_recording">现在开始记录新的轨迹.</string>
<string name="status_stopped_recording">停止记录你的轨迹.</string>
<string name="status_new_mymap_has_been_created">已在 maps.google.com 创建新 Google My Map.</string>
<string name="status_tracks_have_been_uploaded">你的轨迹已经发送到 maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">你的轨迹已经作为新的电子表格发送到 docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">你的轨迹已经发送到 docs.google.com.</string>
<string name="status_mymap_info">新增的轨迹可能需要等待一段时间才会显示到页面上.</string>
<string name="status_enable_gps">请点击这里进入\'安全和位置设置\'并勾选上\'启用 GPS 卫星\'. 然后用后退键返回.</string>
<string name="status_approximate_location">注意: 当前显示的位置是根据无线信号和无线网络计算的近似位置.</string>
<string name="status_no_location">目前无法定位. 请启用更多的位置来源 (在\'安全和位置设置\'中) 并确认天空是可见的. 再重试.</string>
<string name="status_waypoint_inserted">插入了一个路点标记.</string>
<string name="status_statistics_inserted">插入了一个统计标记.</string>
<!-- Progress dialog -->
<string name="progress_title">请稍候...</string>
<string name="progress_message">发送你的轨迹到 Google.</string>
<string name="progress_message_reading_track">读取轨迹中...</string>
<string name="progress_message_preparing_track">准备轨迹中...</string>
<string name="progress_message_authenticating_mymaps">登录 Google Maps 中...</string>
<string name="progress_message_authenticating_docs">登录 Google Docs 中...</string>
<string name="progress_message_sending_docs">发送轨迹到 Google Docs 中...</string>
<string name="progress_message_sending_mymaps">发送轨迹到 Google Maps 中...</string>
<string name="progress_message_creating_map">创建新的 My Map 中...</string>
<string name="import_progress_message">导入 GPS 轨迹中. 请稍候.</string>
<string name="write_progress_message">写入文件到 SD 卡中. 请稍候.</string>
<string name="progress_message_updating_track">更新轨迹统计信息中...</string>
<!-- Used by DeleteAllDataHandler -->
<string name="are_you_sure_question">请确认</string>
<string name="all_data_will_be_permanently_deleted">所以轨迹和标记都将被从本机删除.</string>
<string name="track_will_be_permanently_deleted">选定轨迹和它相关的标记都将被从本机彻底删除.</string>
<string name="marker_will_be_permanently_deleted">选定标记将被从本机删除.</string>
<!-- Text and links when uploading to Google My Maps -->
<string name="new_map_description">由 Android 上的 My Tracks 生成.</string>
<string name="my_map_link">由 Android 上的 &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; 生成.</string>
<string name="new_map_title">新地图</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">点数</string>
<string name="recorded_date">记录日期</string>
<string name="category">活动类型</string>
<string name="waypoint_category">标记类型</string>
<string name="part">(第 %d 部分)</string>
<!-- Used by the track recording service -->
<string name="recording_your_track">记录轨迹中...</string>
<string name="new_track">轨迹 %d</string>
<!-- Common -->
<string name="no">否</string>
<string name="yes">是</string>
<string name="ok">确定</string>
<string name="error">错误</string>
<string name="success">成功</string>
<string name="waypoint">路点</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(开始)</string>
<string name="end">(结束)</string>
<!-- TrackList -->
<string name="tracklist_show_track">显示</string>
<string name="tracklist_send_to_mymaps">发送到 Google My Maps</string>
<string name="tracklist_send_to_new_mymap">发送到新建 Google My Map</string>
<string name="tracklist_send_to_docs">发送到 Google Docs</string>
<string name="tracklist_send_to_google">发送到 Google...</string>
<string name="tracklist_delete_track">从手机上删除...</string>
<string name="tracklist_edit_track">编辑详细信息</string>
<string name="tracklist_write_to_sd">保存到 SD 卡...</string>
<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 -->
<string name="tracklist_btn_export_all">导出所有轨迹</string>
<string name="export_done">导出完毕</string>
<string name="tracklist_pick_one">选择一个轨迹. 长按弹出操作菜单.</string>
<string name="tracklist_all_tracks">全部轨迹:</string>
<string name="tracklist_this_track">此轨迹</string>
<string name="tracklist_share_track">与朋友分享...</string>
<string name="tracklist_share_link">链接到 Google My Map</string>
<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 -->
<string name="waypointslist_btn_delete_all">删除所有标记</string>
<string name="waypointslist_this_waypoint">此标记</string>
<string name="waypointslist_delete_waypoint">删除</string>
<string name="waypointslist_edit_waypoint">编辑详情</string>
<string name="waypointslist_show_waypoint">在地图上显示</string>
<string name="waypointslist_btn_insert_waypoint">插入路点</string>
<string name="waypointslist_btn_insert_statistics">插入统计信息</string>
<string name="waypointslist_pick_one">选择一个标记. 长按弹出操作菜单.</string>
<!-- MyTracks Details -->
<string name="trackdetails_title">轨迹详细信息</string>
<string name="trackdetails_name">轨迹名称</string>
<string name="trackdetails_description">轨迹描述</string>
<string name="trackdetails_category">活动类型 (例如: 徒步, 骑行, 跑步, 攀岩...)</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">标记详细信息</string>
<string name="waypointdetails_name">标记名称</string>
<string name="waypointdetails_description">标记描述</string>
<string name="waypointdetails_category">标记类型 (例如: 建筑, 陷坑, 景点...)</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks 设置</string>
<string name="settings_display">显示</string>
<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_min_recording_distance">最小记录间距</string>
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
<string name="settings_max_recording_distance">最大记录间距</string>
<string name="settings_max_recording_distance_summary">如果记录点之间的距离大于这个值, 轨迹将被切割</string>
<string name="settings_min_recording_interval">最小记录时间间隔</string>
<string name="settings_min_recording_interval_summary">记录位置的最小时间间隔</string>
<string name="settings_min_required_accuracy">最小精度</string>
<string name="settings_min_required_accuracy_summary">如果位置信息达不到此精度, 记录将被暂停</string>
<string name="settings_metric_units">使用公制单位</string>
<string name="settings_metric_units_summary_on">所有的距离都将以公制单位显示</string>
<string name="settings_metric_units_summary_off">所有的距离都将以英制单位显示</string>
<string name="settings_map_public">公开地图</string>
<string name="settings_map_public_summary_on">上传的地图将对所有 maps.google.com 访问者可见</string>
<string name="settings_map_public_summary_off">上传的地图不会在 maps.google.com 上公开</string>
<string name="settings_report_speed_or_pace">显示速度</string>
<string name="settings_report_speed_or_pace_summary_on">显示速度信息</string>
<string name="settings_report_speed_or_pace_summary_off">显示步速</string>
<string name="settings_share_url_only">只分享 URL</string>
<string name="settings_share_url_only_summary_on">在分享时只发送地图的 URL</string>
<string name="settings_share_url_only_summary_off">在分享时也包括轨迹的内容</string>
<string name="settings_signal_sampling_frequency">信号采样频率</string>
<string name="settings_signal_sampling_frequency_summary">记录电话信号强度的时间间隔</string>
<string name="settings_split_frequency">切割频度</string>
<string name="settings_split_frequency_summary">插入新切割标记所需要达到的最大时间或距离</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>
<!-- My Maps -->
<string name="select_map_message">从你的帐户里选择地图:</string>
<string name="create_new_map">创建新地图...</string>
<string name="map_list_title">发送到 Google My Maps</string>
<string name="maps_list_loading">载入地图中...</string>
<string name="maps_list_failed">现在无法载入你的地图!</string>
<string name="open_map">选择此地图</string>
<string name="edit_map_details">编辑地图详细信息</string>
<string name="share_map">与朋友分享地图</string>
<string name="share_track">与朋友分享轨迹</string>
<string name="delete_map">删除你帐号下的此地图</string>
<string name="map_title">地图标题:</string>
<string name="map_description">地图描述:</string>
<string name="map_searchable_edit">允许此地图及其内容出现在搜索结果中</string>
<string name="map_not_searchable_edit">不允许地图及其内容出现在搜索结果中, 但它的内容依然可以通过网址直接访问</string>
<string name="map_searchable">此地图及其内容会出现在搜索结果中</string>
<string name="map_not_searchable">此地图及其内容不会出现在搜索结果中, 但它的内容依然可以通过网址直接访问</string>
<string name="close">关闭</string>
<string name="edit">编辑</string>
<string name="cancel">取消</string>
<string name="save">保存</string>
<!-- Sharing -->
<string name="share_map_subject">我想与你分享一张地图</string>
<string name="share_map_body_format">我想你可能会对这个地图感兴趣:
%s</string>
<string name="send_track_subject">我想与你分享一条轨迹</string>
<string name="send_track_body_format">我想你可能会对这条轨迹感兴趣.
请用 Google Earth 或其它地图软件查看此轨迹.</string>
<!-- Visibility -->
<string name="public_map">公开</string>
<string name="unlisted_map">未公布</string>
<string name="mapslist_no_maps_available">你还没有保存任何地图.</string>
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_label">平均移动速度</string>
<string name="average_moving_pace_label">平均移动步速</string>
<!-- Average -->
<string name="average_speed_label">平均速度</string>
<string name="average_pace_label">平均步速</string>
<string name="elevation_chart_name">海拔图表</string>
<string name="elevation_gain_label">爬升</string>
<string name="elevation_label">海拔</string>
<string name="latitude_label">纬度</string>
<string name="longitude_label">经度</string>
<!-- Min -->
<string name="min_elevation_label">最小海拔</string>
<string name="min_grade_label">最小落差</string>
<string name="min_pace_label">最小步速</string>
<!-- Max -->
<string name="max_elevation_label">最大海拔</string>
<string name="max_grade_label">最大落差</string>
<string name="max_speed_label">最大速度</string>
<string name="loading_track">载入轨迹中...</string>
<!-- Chart Settings -->
<string name="chart_settings">图表设置</string>
<!-- X Axis -->
<string name="x_axis">横轴</string>
<string name="by_distance">按距离显示</string>
<string name="by_time">按时间显示</string>
<!-- Y Axis -->
<string name="y_axis">纵轴</string>
<!-- Units -->
<!-- Time -->
<string name="hour">小时</string>
<string name="hours">小时</string>
<string name="hr">时</string>
<string name="min">分</string>
<string name="minute">分钟</string>
<string name="minutes">分钟</string>
<string name="second">秒</string>
<string name="seconds">秒</string>
<!-- Distance -->
<string name="kilometers_long">千米</string>
<string name="miles_long">英里</string>
<!-- Speed and Pace -->
<string name="kilometer_per_hour_long">千米每小时</string>
<string name="per_kilometer">每千米</string>
<string name="mile_per_hour_long">英里每小时</string>
<string name="per_mile">每英里</string>
<!-- Stats Labels -->
<string name="moving_time_label">移动时间</string>
<string name="total_time_label">总时间</string>
<string name="time_label">时间</string>
<string name="sd_unavail">没有 SD 卡</string>
<string name="speed">速度</string>
<string name="start_trip">你必须先记录轨迹.</string>
<string name="statistics">统计</string>
<string name="total_distance_label">总距离</string>
<string name="distance_label">距离</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 -->
<string name="eula_title">最终用户协议</string>
<string name="accept">同意</string>
<string name="decline">拒绝</string>
<!-- About -->
<string name="about">关于</string>
<string name="help">帮助</string>
<string name="about_team">开发团队:</string>
<string name="about_version">版本:</string>
<string name="about_email">技术支持</string>
<string name="about_support">技术支持:</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">发送到...</string>
<string name="sendtogoogle_send_now">立即发送</string>
<string name="sendtogoogle_create_new_map">创建新地图</string>
<string name="sendtogoogle_pick_existing_map">选择已有地图</string>
<string name="sendtogoogle_send_stats">发送轨迹统计信息</string>
<string name="sendtogoogle_send_stats_and_points">发送统计信息和路点</string>
<!-- Getting the account to upload to. -->
<string name="no_account_found_title">没有帐户</string>
<string name="no_account_found">没有可用于上传轨迹的帐户.</string>
<string name="choose_account_title">选择帐户</string>
<!-- 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_write_finished">轨迹已导出到 SD 卡.</string>
<string name="io_read_finished">从 SD 存储卡读取完毕.</string>
<!-- VersionChecker -->
<string name="version_check_title">检查更新</string>
<string name="version_check_upgrade_button">立即更新</string>
<string name="version_check_continue_button">放弃更新并继续</string>
<string name="upgrade_required">你所使用的 %1$s 版本可能无法继续正常工作, 技术支持也将结束. 请升级以便继续使用 %1$s.</string>
<string name="upgrade_recommended">你所使用的 %1$s 版本可能无法继续正常工作. 你可以现在就更新它, 或者自负风险继续使用. 一些如服务器同步之类的功能在更新前可能不能使用.</string>
<string name="upgrade_available">%1$s 的新版本已经发布. 你可以继续使用现在的版本, 但在更新前无法使用到最新的功能.</string>
<!-- Welcome -->
<string name="welcome">欢迎</string>
<string name="welcome_read_later">确定</string>
<string name="welcome_about">关于 My Tracks</string>
<!-- Announcement -->
<string name="announce_no_distance">My Tracks 无距离</string>
<!-- Format:
$1 : Distance label
$2 : Distance value (5)
$3 : Distance units (mi, km)
$4 : Time description (10 minutes 5 seconds)
$5 : Speed value (5.6) : Pace value (5 minutes 6 seconds)
$6 : Speed units (mph) : Pace units (per mile)
-->
<string name="announce_template">My Tracks %1$s %2$.2f %3$s 用时 %4$s 速度 %5$s %6$s</string>
<!-- Check Units -->
<string name="check_units_title">测量单位提示</string>
<string name="check_units_message">默认单位均为公制. 你想要修改此设置吗?</string>
<!-- Signal Strength -->
<string name="error_rate"> 错误率: %1$d</string>
<string name="signal_to_noise_ratio"> 信噪比: %1$d</string>
<string name="ecio"> Ec/lo: %1$.1f dB</string>
<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>
-18
View File
@@ -236,24 +236,6 @@ 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">
+4 -13
View File
@@ -15,9 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Keys for application preferences - DO NOT TRANSLATE -->
<!-- These are constants used internally for 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>
@@ -29,7 +28,9 @@ limitations under the License.
<string name="min_required_accuracy_key">minRequiredAccuracy</string>
<string name="modular_stats_display_key">modularStatsDisplay</string>
<string name="pick_existing_map_key">pickExistingMap</string>
<string name="recording_track_key">recordingTrack</string>
<string name="report_speed_key">reportSpeed</string>
<string name="selected_track_key">selectedTrack</string>
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
<string name="send_to_docs_key">sendToDocs</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
@@ -38,14 +39,4 @@ 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>
-29
View File
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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>
+7 -31
View File
@@ -32,7 +32,6 @@ 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>
@@ -168,10 +167,9 @@ limitations under the License.
<string name="settings_recording">Recording</string>
<string name="settings_sharing">Sharing</string>
<string name="settings_not_available_summary">Not available in your version of Android</string>
<string name="settings_announcement_frequency">Announcement frequency</string>
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string>
<string name="settings_announcement_not_available_summary">Text-to-speech announcements are not available in your version of Android</string>
<string name="settings_min_recording_distance">Min distance between points</string>
<string name="settings_min_recording_distance_summary">Smallest distance between two recordings of your position</string>
@@ -201,48 +199,28 @@ 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>
<string name="settings_sensor_type">Sensor Type</string>
<string name="settings_sensor_type_summary">The type of sensor to connect to.</string>
<string name="settings_sensor_id">Sensor Id</string>
<string name="settings_sensor_id_summary">The Id sent to sensors.</string>
<string name="sensor_id_malformed">The Id must be an integer value.</string>
<string name="bluetooth_sensor_title">Bluetooth sensor</string>
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to.</string>
<string name="settings_modular_stats_display_title">Modular Stats Display</string>
<string name="settings_modular_stats_display_summary_on">The stats screen is split in various modes.</string>
<string name="settings_modular_stats_display_summary_off">Display all stats on one screen</string>
<string name="settings_backup">Backup (BETA)</string>
<string name="settings_sd_backup">SD Card Backup</string>
<string name="settings_backup_to_sd">Backup now</string>
<string name="settings_backup_to_sd_summary">Write a full copy of all your tracks and settings to the SD card</string>
<string name="settings_restore_from_sd">Restore now</string>
<string name="settings_restore_from_sd_summary">Restore all tracks and settings from the SD card</string>
<string name="settings_no_backup_while_recording">Cannot manipulate backups while recording a track.</string>
<!-- Backups -->
<string name="restore_overwrites_warning">Restoring a backup from the SD card will OVERWRITE all your current tracks and settings. Are you sure?</string>
<string name="select_backup_to_restore">Select the backup to restore</string>
<string name="no_backups">No backups found.</string>
<string name="backup_write_progress_message">Writing all tracks and settings to the SD card. This may take a while...</string>
<string name="backup_import_progress_message">Reading all tracks and settings from the SD card. This may take a while...</string>
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS Satellites</string>
@@ -413,10 +391,8 @@ limitations under the License.
<!-- SD card -->
<string name="io_no_external_storage_found">No external storage was found. Make sure that a SD card is inserted and that your phone is not mounted to a computer.</string>
<string name="io_write_failed">Unable to write to file.</string>
<string name="io_read_failed">Unable to read from file.</string>
<string name="io_create_dir_failed">Unable to create directory on SD card.</string>
<string name="io_write_finished">Finished writing to SD card.</string>
<string name="io_read_finished">Finished reading from SD card.</string>
<string name="io_write_finished">Finished writing the track to SD card.</string>
<!-- VersionChecker -->
<string name="version_check_title">Upgrade Check</string>
-32
View File
@@ -91,22 +91,6 @@ 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 -->
@@ -125,20 +109,4 @@ limitations under the License.
android:summaryOff="@string/settings_share_url_only_summary_off"
android:defaultValue="false" />
</PreferenceScreen>
<!-- Backup settings -->
<PreferenceScreen android:title="@string/settings_backup">
<PreferenceCategory android:title="@string/settings_sd_backup">
<Preference
android:key="@string/backup_to_sd_key"
android:title="@string/settings_backup_to_sd"
android:summary="@string/settings_backup_to_sd_summary"
android:persistent="false" />
<Preference
android:key="@string/restore_from_sd_key"
android:title="@string/settings_restore_from_sd"
android:summary="@string/settings_restore_from_sd_summary"
android:persistent="false" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceScreen>
@@ -25,8 +25,5 @@ public class MyMapsConstants {
"http://maps.google.com/maps/ms";
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
/**
* Private constructor to prevent instantiation.
*/
private MyMapsConstants() { }
}
@@ -0,0 +1,197 @@
/*
* 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();
}
});
}
}
@@ -1,115 +0,0 @@
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,10 +21,8 @@ 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;
@@ -177,15 +175,11 @@ 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);
providerUtils.fillLocation(cursor, location);
if (MyTracksUtils.isValidLocation(location)) {
double[] point = new double[3];
location = getDataPoint(location, track, point);
data.add(point);
Location location = providerUtils.createLocation(cursor);
if (location != null && MyTracksUtils.isValidLocation(location)) {
data.add(getDataPoint(location, track));
}
} while (cursor.moveToPrevious());
cv.addDataPoints(data);
@@ -243,11 +237,6 @@ public class ChartActivity extends Activity implements
MyTracks.getInstance().setChartActivity(this);
providerUtils = MyTracksProviderUtils.Factory.get(this);
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
setVolumeControlStream(volumeStream);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_elevation);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
@@ -298,13 +287,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 (recordingTrackId < 0) {
// recording track:
if (!MyTracks.getInstance().isRecording()) {
// 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;
}
@@ -455,8 +444,7 @@ public class ChartActivity extends Activity implements
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MyTracksConstants.MENU_CHART_SETTINGS:
MyTracks.getInstance().getDialogManager().showDialogSafely(
DialogManager.DIALOG_CHART_SETTINGS);
MyTracks.getInstance().showDialogSafely(MyTracks.DIALOG_CHART_SETTINGS);
return true;
}
return super.onOptionsItemSelected(item);
@@ -469,14 +457,11 @@ public class ChartActivity extends Activity implements
* data[1] = the elevation
* data[2] = the speed
*
* 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
* @param location a location
* @return the data point
*/
private Location getDataPoint(Location location, Track track, double[] result) {
public double[] getDataPoint(Location location, Track track) {
double[] result = new double[3];
switch (mode) {
case BY_DISTANCE:
result[0] = profileLength;
@@ -523,16 +508,8 @@ public class ChartActivity extends Activity implements
// Format as hours per unit
result[2] = (60.0 / result[2]);
}
Location oldLastLocation = lastLocation;
lastLocation = location;
if (oldLastLocation == null) {
// No previous location, but return a blank one for reuse
return new Location("");
}
return oldLastLocation;
return result;
}
/**
@@ -575,8 +552,6 @@ 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);
@@ -588,12 +563,11 @@ public class ChartActivity extends Activity implements
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
while (cursor.moveToNext()) {
points++;
providerUtils.fillLocation(cursor, location);
Location location = providerUtils.createLocation(cursor);
if (MyTracksUtils.isValidLocation(location)) {
lastLocationRead = lastSeenLocationId =
cursor.getLong(idColumnIdx);
double[] point = new double[3];
location = getDataPoint(location, track, point);
double[] point = getDataPoint(location, track);
if (points % chartSamplingFrequency == 0) {
theData.add(point);
}
@@ -66,10 +66,6 @@ public class ChartSettingsDialog extends Dialog {
}
public void setup(ChartActivity chart) {
if (chart == null) {
return;
}
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
rd.check(chart.getMode() == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance
@@ -25,7 +25,7 @@ import android.graphics.Path;
import java.text.DecimalFormat;
/**
* This class encapsulates meta data about one series of values for a chart.
* This class encasulates meta data about one series of values for a chart.
*
* @author Sandor Dornbush
*/
@@ -511,8 +511,8 @@ public class ChartView extends View {
// Dimensions have changed (for example due to orientation change):
w = c.getWidth();
h = c.getHeight();
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
effectiveHeight = Math.max(0, h - TOP_BORDER - BOTTOM_BORDER);
effectiveWidth = w - leftBorder - RIGHT_BORDER;
effectiveHeight = h - TOP_BORDER - BOTTOM_BORDER;
setupPath();
}
c.save();
@@ -1,231 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.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.
}
}
});
}
}
@@ -17,12 +17,10 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.TrackWriter;
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;
@@ -47,7 +45,7 @@ public class ExportAllTracks {
public static final int KML_OPTION_INDEX = 1;
public static final int CSV_OPTION_INDEX = 2;
private final Activity activity;
private final Context context;
private WakeLock wakeLock;
private ProgressDialog progress;
@@ -71,11 +69,11 @@ public class ExportAllTracks {
}
};
public ExportAllTracks(Activity activity) {
this.activity = activity;
public ExportAllTracks(Context c) {
this.context = c;
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Starting");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
AlertDialog.Builder builder = new AlertDialog.Builder(c);
builder.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
builder.setPositiveButton(R.string.ok, positiveClick);
builder.setNegativeButton(R.string.cancel, null);
@@ -108,11 +106,11 @@ public class ExportAllTracks {
*/
private void aquireLocksAndExport() {
SharedPreferences prefs =
activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
long recordingTrackId = -1;
if (prefs != null) {
recordingTrackId =
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
prefs.getLong(context.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
acquireWakeLock();
@@ -128,12 +126,12 @@ public class ExportAllTracks {
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
}
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Done");
Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.export_done, Toast.LENGTH_SHORT).show();
}
private void makeProgressDialog(final int trackCount) {
String exportMsg = activity.getString(R.string.tracklist_btn_export_all);
progress = new ProgressDialog(activity);
String exportMsg = context.getString(R.string.tracklist_btn_export_all);
progress = new ProgressDialog(context);
progress.setIcon(android.R.drawable.ic_dialog_info);
progress.setTitle(exportMsg);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
@@ -151,25 +149,22 @@ public class ExportAllTracks {
Cursor cursor = null;
try {
MyTracksProviderUtils providerUtils =
MyTracksProviderUtils.Factory.get(activity);
MyTracksProviderUtils.Factory.get(context);
cursor = providerUtils.getTracksCursor("");
if (cursor == null) {
return;
}
final int trackCount = cursor.getCount();
Log.i(MyTracksConstants.TAG,
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
activity.runOnUiThread(new Runnable() {
MyTracks.getInstance().runOnUiThread(new Runnable() {
public void run() {
makeProgressDialog(trackCount);
}
});
}});
for (int i = 0; cursor.moveToNext(); i++) {
final int status = i;
activity.runOnUiThread(new Runnable() {
MyTracks.getInstance().runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progress == null) {
@@ -182,27 +177,16 @@ public class ExportAllTracks {
long id = cursor.getLong(idxTrackId);
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
TrackWriter writer =
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(activity, error, Toast.LENGTH_LONG).show();
return;
}
TrackWriterFactory.newWriter(context, providerUtils, id, format)
.writeTrack();
}
} finally {
if (cursor != null) {
cursor.close();
}
if (progress != null) {
synchronized (this) {
progress.dismiss();
progress = null;
}
}
}
if (progress != null) {
progress.dismiss();
}
}
@@ -214,7 +198,7 @@ public class ExportAllTracks {
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
try {
PowerManager pm =
(PowerManager) activity.getSystemService(Context.POWER_SERVICE);
(PowerManager) context.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 final MyTracks activity;
private 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);
menu.findItem(R.id.menu_start_recording)
.setEnabled(!isRecording)
.setVisible(!isRecording);
menu.findItem(R.id.menu_stop_recording)
.setEnabled(isRecording)
.setVisible(isRecording);
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);
}
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.getSelectedTrackId());
startIntent.putExtra("trackid", activity.getSelectedTrack());
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
return true;
}
@@ -78,18 +78,13 @@ 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.setSelectedTrackId(-1);
activity.setSelectedTrack(-1);
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -32,12 +32,6 @@ public abstract class MyTracksConstants {
*/
public static final String GPS_PROVIDER = "gps";
/**
* Name of the top-level directory inside the SD card where our files will
* be read from/written to.
*/
public static final String SDCARD_TOP_DIR = "MyTracks";
/*
* onActivityResult request codes:
*/
@@ -172,13 +166,6 @@ 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,9 +21,7 @@ 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;
@@ -51,14 +49,14 @@ import android.os.HandlerThread;
import android.provider.Settings;
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.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.view.Window;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -71,9 +69,8 @@ import android.widget.Toast;
* @author Leif Hendrik Wilden
*/
public class MyTracksMap extends MapActivity
implements View.OnTouchListener, View.OnClickListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private static final int TRACKPOINT_BUFFER_SIZE = 1024;
implements LocationListener, SensorEventListener, View.OnTouchListener,
View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
// Saved instance state keys:
// ---------------------------
@@ -84,9 +81,9 @@ public class MyTracksMap extends MapActivity
public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix";
/**
* The ID of the currently selected track (or -1 if nothing selected).
* The currently selected track (or null if nothing selected).
*/
private long selectedTrackId = -1;
private Track selectedTrack;
/**
* The id of the currently recording track.
@@ -99,26 +96,20 @@ 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;
@@ -135,31 +126,32 @@ public class MyTracksMap extends MapActivity
private Location currentLocation;
/**
* A thread with a looper. Post to updateTrackHandler to execute
* {@link Runnable}s on this thread.
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
* this thread.
*/
private HandlerThread updateTrackThread;
private final HandlerThread updateTrackThread =
new 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 (!isATrackSelected()) {
if (selectedTrack == null) {
return;
}
readAllNewTrackPoints();
}
};
@@ -170,13 +162,11 @@ public class MyTracksMap extends MapActivity
private Runnable restoreTrackRunnable = new Runnable() {
@Override
public void run() {
if (!isATrackSelected()) {
if (selectedTrack == null) {
return;
}
mapOverlay.clearPoints();
firstSeenLocationId = -1;
lastSeenLocationId = -1;
lastSeenLocationId = selectedTrack.getStartId();
readAllNewTrackPoints();
}
};
@@ -187,7 +177,7 @@ public class MyTracksMap extends MapActivity
private final Runnable restoreWaypointsRunnable = new Runnable() {
@Override
public void run() {
if (!isATrackSelected()) {
if (selectedTrack == null) {
return;
}
@@ -197,12 +187,15 @@ 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(
selectedTrackId, 0,
selectedTrack.getId(), 0,
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
if (cursor != null && cursor.moveToFirst()) {
do {
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
} while (cursor.moveToNext());
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
Waypoint wpt = providerUtils.createWaypoint(cursor);
mapOverlay.addWaypoint(wpt);
} while (cursor.moveToNext());
}
}
} catch (RuntimeException e) {
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
@@ -216,18 +209,19 @@ public class MyTracksMap extends MapActivity
};
/**
* 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.
* 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.
*/
private final Runnable setSelectedTrackRunnable = new Runnable() {
@Override
public void run() {
uiHandler.post(new Runnable() {
public void run() {
showTrack(selectedTrackId);
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
mapOverlay.setShowEndMarker(!isRecordingSelected());
showTrack(selectedTrack);
mapOverlay.setSelectedTrack(selectedTrack);
mapOverlay.setShowEndMarker(selectedTrack == null ||
selectedTrack.getId() != recordingTrackId);
mapView.invalidate();
busyPane.setVisibility(View.GONE);
updateOptionsButton();
@@ -285,11 +279,6 @@ public class MyTracksMap extends MapActivity
Log.d(MyTracksConstants.TAG, "MyTracksMap.onCreate");
super.onCreate(bundle);
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
setVolumeControlStream(volumeStream);
providerUtils = MyTracksProviderUtils.Factory.get(this);
// We don't need a window title bar:
@@ -323,7 +312,6 @@ public class MyTracksMap extends MapActivity
locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
updateTrackThread = new HandlerThread("updateTrackThread");
updateTrackThread.start();
updateTrackHandler = new Handler(updateTrackThread.getLooper());
@@ -333,7 +321,14 @@ public class MyTracksMap extends MapActivity
@Override
public void onChange(boolean selfChange) {
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
if (!isRecordingSelected()) {
// 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) {
// No track, or one other than the recording track is selected,
// don't bother.
return;
@@ -349,7 +344,7 @@ public class MyTracksMap extends MapActivity
public void onChange(boolean selfChange) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: ContentObserver.onChange waypoints");
if (!isATrackSelected()) {
if (selectedTrack == null) {
return;
}
updateTrackHandler.post(restoreWaypointsRunnable);
@@ -357,49 +352,38 @@ public class MyTracksMap extends MapActivity
}
};
// Read shared preferences and register change listener.
sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (sharedPreferences != null) {
reloadSharedPreferences(sharedPreferences, null);
// 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);
}
updateOptionsButton();
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
preferences.registerOnSharedPreferenceChangeListener(this);
}
}
@SuppressWarnings("deprecation")
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.
@@ -442,9 +426,6 @@ 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);
@@ -465,9 +446,10 @@ 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:
if (isATrackSelected() && !providerUtils.trackExists(selectedTrackId)) {
Track track = mapOverlay.getSelectedTrack();
if (track != null && !providerUtils.trackExists(track.getId())) {
// The recording track must have been deleted meanwhile.
mapOverlay.setTrackDrawingEnabled(false);
mapOverlay.setSelectedTrack(null);
mapView.invalidate();
}
}
@@ -529,10 +511,10 @@ public class MyTracksMap extends MapActivity
+ gpsProvider.getName());
}
locationManager.requestLocationUpdates(gpsProvider.getName(),
0 /*minTime*/, 0 /*minDist*/, locationListener);
0 /*minTime*/, 0 /*minDist*/, this);
try {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener);
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this);
} 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.
@@ -550,7 +532,7 @@ public class MyTracksMap extends MapActivity
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now registering sensor listeners.");
sensorManager.registerListener(
sensorListener, compass, SensorManager.SENSOR_DELAY_UI);
this, compass, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -560,12 +542,12 @@ public class MyTracksMap extends MapActivity
if (locationManager != null) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now unregistering location listeners.");
locationManager.removeUpdates(locationListener);
locationManager.removeUpdates(this);
}
if (sensorManager != null) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Now unregistering sensor listeners.");
sensorManager.unregisterListener(sensorListener);
sensorManager.unregisterListener(this);
}
}
@@ -594,7 +576,7 @@ public class MyTracksMap extends MapActivity
*/
private void updateOptionsButton() {
optionsBtn.setVisibility(
isATrackSelected() ? View.VISIBLE : View.INVISIBLE);
selectedTrack != null ? View.VISIBLE : View.INVISIBLE);
}
/**
@@ -647,31 +629,21 @@ public class MyTracksMap extends MapActivity
/**
* Zooms and pans the map so that the given track is visible.
*
* @param trackId a given track ID
* @param track a given track
*/
public void showTrack(long trackId) {
if (mapView == null) {
public void showTrack(Track track) {
if (track == null || mapView == null || track.getNumberOfPoints() < 2) {
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();
int latSpanE6 = stats.getTop() - bottom;
int lonSpanE6 = stats.getRight() - left;
if (latSpanE6 > 0
&& latSpanE6 < 180E6
&& lonSpanE6 > 0
&& lonSpanE6 < 360E6) {
int latSpanE6 = stats.getTop() - stats.getBottom();
int lonSpanE6 = stats.getRight() - stats.getLeft();
if (latSpanE6 > 0 && latSpanE6 < 180E6 && lonSpanE6 > 0
&& lonSpanE6 < 180E6) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint(
bottom + latSpanE6 / 2,
left + lonSpanE6 / 2);
stats.getBottom() + latSpanE6 / 2,
stats.getLeft() + lonSpanE6 / 2);
if (MyTracksUtils.isValidGeoPoint(center)) {
mapView.getController().setCenter(center);
mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
@@ -701,33 +673,24 @@ public class MyTracksMap extends MapActivity
* @param trackId a given track id
*/
public void setSelectedTrack(final long trackId) {
Log.d(MyTracksConstants.TAG, "MyTracksMap.setSelectedTrack: "
+ "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId);
if (selectedTrackId == trackId) {
if (selectedTrack != null && selectedTrack.getId() == trackId) {
// Selected track did not change, nothing to do.
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
updateOptionsButton();
mapOverlay.setSelectedTrack(selectedTrack);
mapView.invalidate();
updateOptionsButton();
return;
}
if (trackId < 0) {
// Remove selection.
selectedTrackId = -1;
mapOverlay.setTrackDrawingEnabled(false);
selectedTrack = null;
mapOverlay.setSelectedTrack(null);
mapOverlay.clearWaypoints();
updateOptionsButton();
mapView.invalidate();
return;
}
busyPane.setVisibility(View.VISIBLE);
selectedTrackId = trackId;
loadSelectedTrack();
}
private void loadSelectedTrack() {
selectedTrack = providerUtils.getTrack(trackId);
updateTrackHandler.post(restoreTrackRunnable);
updateTrackHandler.post(restoreWaypointsRunnable);
updateTrackHandler.post(setSelectedTrackRunnable);
@@ -783,7 +746,8 @@ 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 (!isRecordingSelected()) {
if (!MyTracks.getInstance().isRecording() || (selectedTrack != null
&& selectedTrack.getId() != recordingTrackId)) {
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
@@ -820,7 +784,7 @@ public class MyTracksMap extends MapActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (!super.onMenuItemSelected(featureId, item)) {
if (isATrackSelected()) {
if (selectedTrack != null) {
MyTracks.getInstance().onActivityResult(
MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK,
new Intent());
@@ -881,7 +845,8 @@ public class MyTracksMap extends MapActivity
public void onClick(View v) {
if (v == messagePane) {
launchMyLocationSettings();
} else if (v == optionsBtn) {
}
if (v == optionsBtn) {
optionsBtn.performLongClick();
}
}
@@ -901,190 +866,156 @@ public class MyTracksMap extends MapActivity
}
@Override
public void onSharedPreferenceChanged(
final SharedPreferences sharedPreferences, final String key) {
Log.d(MyTracksConstants.TAG,
"MyTracksMap.onSharedPreferenceChanged: " + key);
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key != null) {
uiHandler.post(new Runnable() {
@Override
public void run() {
reloadSharedPreferences(sharedPreferences, key);
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();
}
});
}
}
}
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);
}
@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(MyTracksMap.this);
@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);
}
currentLocation = location;
boolean haveGoodFixNow =
currentLocation.getAccuracy() < minRequiredAccuracy;
if (haveGoodFixNow != haveGoodFix) {
haveGoodFix = haveGoodFixNow;
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
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);
}
currentLocation = location;
boolean haveGoodFixNow =
currentLocation.getAccuracy() < minRequiredAccuracy;
if (haveGoodFixNow != haveGoodFix) {
haveGoodFix = haveGoodFixNow;
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
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);
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();
showCurrentLocation();
} else {
Log.d(MyTracksConstants.TAG,
"MyTracksMap: Network location update received.");
}
}
private void readAllNewTrackPoints() {
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
@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;
}
}
}
}
@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();
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);
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);
while (cursor.moveToNext()) {
points++;
Location location = providerUtils.createLocation(cursor);
lastSeenLocationId = cursor.getLong(idColumnIdx);
// Include a point if it fits one of the following criteria:
// - Has the mod for the sampling frequency (includes first point).
// - Has the mod for the sampling frequency.
// - Is the first point.
// - Is the last point and we are not recording this track.
// - 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).
if (!MyTracksUtils.isValidLocation(location) ||
points % samplingFrequency == 0 ||
points == 0 ||
(recordingTrackId != selectedTrack.getId() &&
points == (totalLocations - 1))) {
mapOverlay.addLocation(location);
}
numPoints++;
} while (cursor.moveToNext() &&
currentSelectedTrackId == selectedTrackId);
} finally {
if (cursor != null) {
cursor.close();
}
} else {
lastSeenLocationId += bufferSize;
}
cursor.close();
cursor = null;
}
if (cursor != null) {
cursor.close();
}
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,9 +35,6 @@ 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
@@ -47,7 +44,7 @@ import java.util.concurrent.BlockingQueue;
*/
public class MyTracksOverlay extends Overlay {
private final Drawable[] arrows;
private final Drawable arrow[] = new Drawable[18];
private final int arrowWidth, arrowHeight;
private final Drawable statsMarker;
private final Drawable waypointMarker;
@@ -57,152 +54,102 @@ public class MyTracksOverlay extends Overlay {
private final Paint selectedTrackPaint;
private final Paint errorCirclePaint;
private final Context context;
private final List<Waypoint> waypoints;
private final List<CachedLocation> points;
private final BlockingQueue<CachedLocation> pendingPoints;
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
private final ArrayList<Location> points = new ArrayList<Location>(1024);
private boolean trackDrawingEnabled;
private Track selectedTrack;
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?
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);
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);
}
statsMarker = resources.getDrawable(R.drawable.ylw_pushpin);
statsMarker = context.getResources().getDrawable(R.drawable.ylw_pushpin);
markerWidth = statsMarker.getIntrinsicWidth();
markerHeight = statsMarker.getIntrinsicHeight();
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
startMarker = resources.getDrawable(R.drawable.green_dot);
startMarker = context.getResources().getDrawable(R.drawable.green_dot);
startMarker.setBounds(0, 0, markerWidth, markerHeight);
endMarker = resources.getDrawable(R.drawable.red_dot);
endMarker = context.getResources().getDrawable(R.drawable.red_dot);
endMarker.setBounds(0, 0, markerWidth, markerHeight);
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
waypointMarker =
context.getResources().getDrawable(R.drawable.blue_pushpin);
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
selectedTrackPaint = new Paint();
selectedTrackPaint.setColor(resources.getColor(R.color.red));
selectedTrackPaint.setColor(context.getResources().getColor(R.color.red));
selectedTrackPaint.setStrokeWidth(3);
selectedTrackPaint.setStyle(Paint.Style.STROKE);
selectedTrackPaint.setAntiAlias(true);
errorCirclePaint = new Paint();
errorCirclePaint.setColor(resources.getColor(R.color.blue));
errorCirclePaint.setColor(context.getResources().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.
*
* @param l the location to add.
* NOTE: this method takes ownership of this location and may change it.
*
* @param l the location to add
*/
public void addLocation(Location l) {
// Queue up in the pending queue until it's merged with {@code #points}.
pendingPoints.offer(new CachedLocation(l));
points.add(l);
}
public void addWaypoint(Waypoint 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;
}
waypoints.add(wpt);
}
public void clearWaypoints() {
synchronized (waypoints) {
waypoints.clear();
}
waypoints.clear();
}
public void setTrackDrawingEnabled(boolean trackDrawingEnabled) {
this.trackDrawingEnabled = trackDrawingEnabled;
public void clearPoints() {
points.clear();
}
public void setShowEndMarker(boolean showEndMarker) {
@@ -211,179 +158,159 @@ public class MyTracksOverlay extends Overlay {
@Override
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
if (shadow) {
return;
}
// 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 selected track:
drawTrack(canvas, mapView, selectedTrack, true);
// Draw the waypoints:
drawWaypoints(canvas, projection);
// 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 current location
drawMyLocation(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);
}
// Visible for testing.
Projection getMapProjection(MapView mapView) {
return mapView.getProjection();
}
// Visible for testing.
Rect getMapViewRect(MapView mapView) {
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:
int w = mapView.getLongitudeSpan();
int h = mapView.getLatitudeSpan();
int cx = mapView.getMapCenter().getLongitudeE6();
int cy = mapView.getMapCenter().getLatitudeE6();
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
}
// Visible for testing.
Path newPath() {
return new Path();
}
// Visible for testing.
Path getLastPath() {
return lastPath;
}
Rect rect = new Rect(cx - w, cy - h, cx + w, cy + h);
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);
}
}
}
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) {
Point pt = new Point();
GeoPoint geoPoint;
Location loc;
Location lastValidLocation = null;
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;
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;
}
lastReferencePoint = referencePoint;
lastViewRect = viewRect;
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;
}
if (path != null) {
canvas.drawPath(path, selectedTrackPaint);
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 "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);
// 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 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;
@Override
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
long when) {
draw(canvas, mapView, shadow);
return false;
}
/**
@@ -426,22 +353,20 @@ public class MyTracksOverlay extends Overlay {
final Location tapLocation = MyTracksUtils.getLocation(p);
double dmin = Double.MAX_VALUE;
Waypoint waypoint = null;
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);
}
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);
@@ -1,57 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.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");
}
}
}
@@ -15,21 +15,17 @@
*/
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.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
import com.google.android.maps.mytracks.R;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.util.Log;
/**
* An activity that let's the user see and edit the settings.
@@ -44,7 +40,6 @@ 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,38 +47,36 @@ public class MyTracksSettings extends PreferenceActivity {
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
private BackupPreferencesListener backupListener;
private SharedPreferences preferences;
private static boolean mTTSAvailable;
/* 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
ApiFeatures apiFeatures = ApiFeatures.getInstance();
int volumeStream =
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
setVolumeControlStream(volumeStream);
// Tell it where to read/write preferences
PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName(SETTINGS_NAME);
preferenceManager.setSharedPreferencesMode(0);
// Set up automatic preferences backup
backupListener = BackupPreferencesListener.create(this, apiFeatures);
preferences = preferenceManager.getSharedPreferences();
preferences.registerOnSharedPreferenceChangeListener(backupListener);
// Load the preferences to be displayed
addPreferencesFromResource(R.xml.preferences);
// Hook up switching of displayed list entries between metric and imperial
// units
CheckBoxPreference metricUnitsPreference =
(CheckBoxPreference) findPreference(
getString(R.string.metric_units_key));
(CheckBoxPreference) findPreference(getString(R.string.metric_units_key));
metricUnitsPreference.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
@@ -97,70 +90,17 @@ public class MyTracksSettings extends PreferenceActivity {
updatePreferenceUnits(metricUnitsPreference.isChecked());
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
if (!mTTSAvailable) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
R.string.settings_announcement_not_available_summary);
}
}
@Override
protected void onResume() {
super.onResume();
Preference backupNowPreference =
findPreference(getString(R.string.backup_to_sd_key));
Preference restoreNowPreference =
findPreference(getString(R.string.restore_from_sd_key));
// If recording, disable backup/restore
// (we don't want to get to inconsistent states)
boolean recording =
preferences.getLong(getString(R.string.recording_track_key), -1) != -1;
backupNowPreference.setEnabled(!recording);
restoreNowPreference.setEnabled(!recording);
backupNowPreference.setSummary(
recording ? R.string.settings_no_backup_while_recording
: R.string.settings_backup_to_sd_summary);
restoreNowPreference.setSummary(
recording ? R.string.settings_no_backup_while_recording
: R.string.settings_restore_from_sd_summary);
// Add actions to the backup preferences
backupNowPreference.setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
BackupActivityHelper backupHelper =
new BackupActivityHelper(MyTracksSettings.this);
backupHelper.writeBackup();
return true;
}
});
restoreNowPreference.setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
BackupActivityHelper backupHelper =
new BackupActivityHelper(MyTracksSettings.this);
backupHelper.restoreBackup();
return true;
}
});
}
@Override
protected void onDestroy() {
getPreferenceManager().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(backupListener);
super.onDestroy();
}
/**
* Updates all the preferences which give options with distance units to use
* the proper unit the user has selected.
@@ -180,7 +120,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,6 +135,7 @@ 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,8 +15,9 @@
*/
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;
@@ -49,18 +50,21 @@ public class NavControls {
/**
* A touchable image view.
* When touched it changes the navigation control icons accordingly.
*/
private class TouchLayout extends RelativeLayout implements Runnable {
private final boolean isLeft;
public class TouchLayout extends RelativeLayout {
private final ImageView arrow;
private final ImageView icon;
public TouchLayout(Context context, boolean isLeft) {
super(context);
this.isLeft = isLeft;
this.icon = new ImageView(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);
icon.setVisibility(View.GONE);
addView(arrow);
addView(icon);
icon.setPadding((isLeft ? 15 : 10), 27, 15, 0);
}
public void setIcon(Drawable drawable) {
@@ -73,11 +77,18 @@ public class NavControls {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
shiftIcons(isLeft);
// Call the user back
handler.post(this);
hide();
if (this == prevImage) {
if (prevRunnable != null) {
handler.post(prevRunnable);
return true;
}
} else if (this == nextImage) {
if (nextRunnable != null) {
handler.post(nextRunnable);
return true;
}
}
break;
case MotionEvent.ACTION_UP:
setPressed(false);
@@ -85,69 +96,55 @@ 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 int currentIcons;
private boolean hasNext = true;
private boolean hasPrev = true;
public NavControls(Context context, ViewGroup container,
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");
}
Runnable prevRunnable, Runnable nextRunnable) {
this.context = context;
this.prevRunnable = prevRunnable;
this.nextRunnable = nextRunnable;
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);
}
prevImage.setIcon(leftIcons.getDrawable(0));
nextImage.setIcon(rightIcons.getDrawable(0));
this.currentIcons = 0;
public void setHasNext(boolean hasNext) {
this.hasNext = hasNext;
}
public void setHasPrev(boolean hasPrev) {
this.hasPrev = hasPrev;
}
private void keepVisible() {
@@ -159,18 +156,20 @@ public class NavControls {
public void show() {
if (!isVisible) {
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);
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);
}
isVisible = true;
keepVisible();
} else {
@@ -189,26 +188,33 @@ public class NavControls {
public void hide() {
isVisible = false;
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);
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);
}
}
public int getCurrentIcons() {
return currentIcons;
public void setLeftIcon(int resourceId) {
prevImage.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));
public void setRightIcon(int resourceId) {
nextImage.setIcon(context.getResources().getDrawable(resourceId));
}
}
@@ -23,8 +23,7 @@ package com.google.android.apps.mytracks;
*/
public interface ProgressIndicator {
public void setProgressMessage(int resId);
public void clearProgressMessage();
public void setProgressMessage(String text);
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(DialogManager.DIALOG_SEND_TO_GOOGLE);
MyTracks.getInstance().dismissDialog(MyTracks.DIALOG_SEND_TO_GOOGLE);
}
});
@@ -20,8 +20,6 @@ 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;
@@ -50,12 +48,14 @@ import android.widget.Toast;
* @author Sandor Dornbush
*/
public class StatsActivity extends Activity
implements OnSharedPreferenceChangeListener {
implements OnSharedPreferenceChangeListener, LocationListener {
private StatsUtilities utils;
private final StatsUtilities utils;
private UIUpdateThread thread;
private ContentObserver observer;
private LocationManager locationManager;
/**
* The id of the currently selected track.
@@ -94,7 +94,8 @@ public class StatsActivity extends Activity
*/
private boolean showCurrentSegment = false;
private MyTracksProviderUtils providerUtils;
private final MyTracksProviderUtils providerUtils;
private Track track = null;
/**
* A runnable for posting to the UI thread. Will update the total time field.
@@ -133,19 +134,16 @@ 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
int volumeStream =
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
setVolumeControlStream(volumeStream);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -177,9 +175,11 @@ public class StatsActivity extends Activity
preferences.getBoolean(getString(R.string.report_speed_key), true);
checkLiveTrack();
restoreStats();
showUnknownLocation();
updateLocation(null);
preferences.registerOnSharedPreferenceChangeListener(this);
}
locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
utils.setMetricUnits(metricUnits);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
@@ -210,57 +210,49 @@ public class StatsActivity extends Activity
activityOnTop = true;
checkLiveTrack();
restoreStats();
showUnknownLocation();
updateLocation(null);
super.onResume();
}
@Override
public void onSharedPreferenceChanged(
final SharedPreferences sharedPreferences, final String key) {
SharedPreferences sharedPreferences, String key) {
Log.d(MyTracksConstants.TAG,
"StatsActivity: onSharedPreferences changed " + key);
if (key != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (key.equals(getString(R.string.selected_track_key))) {
selectedTrackId =
sharedPreferences.getLong(
getString(R.string.selected_track_key),
-1);
checkLiveTrack();
restoreStats();
showUnknownLocation();
} else if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId =
sharedPreferences.getLong(
getString(R.string.recording_track_key),
-1);
checkLiveTrack();
restoreStats();
showUnknownLocation();
} else if (key.equals(getString(R.string.metric_units_key))) {
metricUnits =
sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
utils.updateUnits();
restoreStats();
} else if (key.equals(getString(R.string.report_speed_key))) {
displaySpeed =
sharedPreferences.getBoolean(
getString(R.string.report_speed_key),
true);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
utils.setSpeedLabel(
R.id.speed_label, R.string.speed, R.string.pace_label);
Log.w(MyTracksConstants.TAG, "Setting speed labels");
utils.setSpeedLabels();
restoreStats();
}
}
});
if (key.equals(getString(R.string.selected_track_key))) {
selectedTrackId =
sharedPreferences.getLong(getString(R.string.selected_track_key),
-1);
checkLiveTrack();
restoreStats();
updateLocation(null);
} else if (key.equals(getString(R.string.recording_track_key))) {
recordingTrackId =
sharedPreferences.getLong(getString(R.string.recording_track_key),
-1);
checkLiveTrack();
restoreStats();
updateLocation(null);
} else if (key.equals(getString(R.string.metric_units_key))) {
metricUnits =
sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
utils.updateUnits();
restoreStats();
} else if (key.equals(getString(R.string.report_speed_key))) {
displaySpeed =
sharedPreferences.getBoolean(getString(R.string.report_speed_key),
true);
utils.setReportSpeed(displaySpeed);
utils.updateUnits();
utils.setSpeedLabel(
R.id.speed_label, R.string.speed, R.string.pace_label);
Log.w(MyTracksConstants.TAG, "Setting speed labels");
utils.setSpeedLabels();
restoreStats();
}
}
}
@@ -295,36 +287,29 @@ public class StatsActivity extends Activity
return super.onOptionsItemSelected(item);
}
private final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location l) {
if (selectedTrackIsRecording()) {
showLocation(l);
}
@Override
public void onLocationChanged(Location l) {
if (selectedTrackIsRecording()) {
updateLocation(l);
}
@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
}
};
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
/**
* 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);
@@ -337,7 +322,7 @@ public class StatsActivity extends Activity
+ gpsProvider.getName());
}
locationManager.requestLocationUpdates(gpsProvider.getName(),
0/*minTime*/, 0/*minDist*/, locationListener);
0/*minTime*/, 0/*minDist*/, this);
}
}
@@ -345,10 +330,8 @@ public class StatsActivity extends Activity
* Unregisters all location listener.
*/
private void unregisterLocationListener() {
LocationManager locationManager =
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
if (locationManager != null) {
locationManager.removeUpdates(locationListener);
locationManager.removeUpdates(this);
}
}
@@ -369,14 +352,9 @@ public class StatsActivity extends Activity
return;
}
Track track = providerUtils.getTrack(selectedTrackId);
if (track == null || track.getStatistics() == null) {
utils.setAllToUnknown();
return;
}
track = providerUtils.getTrack(selectedTrackId);
startTime = track.getStatistics().getStartTime();
if (!selectedTrackIsRecording()) {
if(!selectedTrackIsRecording()) {
utils.setTime(R.id.total_time_register,
track.getStatistics().getTotalTime());
}
@@ -426,17 +404,17 @@ public class StatsActivity extends Activity
*
* @param l may be null (will set location fields to unknown)
*/
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);
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);
}
}
}
@@ -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,8 +230,6 @@ 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) {
@@ -1,32 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.content;
/**
* Utilities for serializing primitive types.
*
* @author Rodrigo Damazio
*/
public class ContentTypeIds {
public static final byte BOOLEAN_TYPE_ID = 0;
public static final byte LONG_TYPE_ID = 1;
public static final byte INT_TYPE_ID = 2;
public static final byte FLOAT_TYPE_ID = 3;
public static final byte DOUBLE_TYPE_ID = 4;
public static final byte STRING_TYPE_ID = 5;
private ContentTypeIds() { /* Not instantiable */ }
}
@@ -15,14 +15,11 @@
*/
package com.google.android.apps.mytracks.content;
import android.content.ContentValues;
import android.content.Context;
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.
*
@@ -175,7 +172,7 @@ public interface MyTracksProviderUtils {
* @return A cursor over the selected range of locations
*/
Cursor getWaypointsCursor(long trackId, long minWaypointId,
int maxWaypoints);
long maxWaypoints);
/**
* Finds a track by given unique track id.
@@ -186,15 +183,6 @@ 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.
@@ -206,6 +194,17 @@ 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.
@@ -268,24 +267,6 @@ public interface MyTracksProviderUtils {
*/
void updateTrack(Track track);
/**
* Creates a Track object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with tracks
* @return a new Track object
*/
Track createTrack(Cursor cursor);
/**
* Creates the ContentValues for a given Track object.
*
* Note: If the track has an id<0 the id column will not be filled.
*
* @param track a given track object
* @return a filled in ContentValues object
*/
ContentValues createContentValues(Track track);
/**
* Creates a location object from a given cursor.
*
@@ -294,14 +275,6 @@ 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.
*
@@ -345,7 +318,7 @@ public interface MyTracksProviderUtils {
* Creates an instance of {@link MyTracksProviderUtils}.
*/
protected MyTracksProviderUtils newForContext(Context context) {
return new MyTracksProviderUtilsImpl(context.getContentResolver());
return new MyTracksProviderUtilsImpl(context);
}
}
}
@@ -17,16 +17,13 @@ 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.
@@ -35,10 +32,10 @@ import java.util.List;
*/
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private final ContentResolver contentResolver;
private final Context context;
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
public MyTracksProviderUtilsImpl(Context context) {
this.context = context;
}
/**
@@ -77,8 +74,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return values;
}
@Override
public ContentValues createContentValues(Track track) {
/**
* Creates the ContentValues for a given Track object.
*
* Note: If the track has an id<0 the id column will not be filled.
*
* @param track a given track object
* @return a filled in ContentValues object
*/
private static ContentValues createContentValues(Track track) {
ContentValues values = new ContentValues();
TripStatistics stats = track.getStatistics();
@@ -171,15 +175,6 @@ 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);
@@ -189,6 +184,7 @@ 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);
}
@@ -210,10 +206,16 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
}
return location;
}
@Override
public Track createTrack(Cursor cursor) {
/**
* Creates a Track object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with tracks
* @return a new Track object
*/
private static Track createTrack(Cursor cursor) {
int idxId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
int idxDescription =
@@ -448,22 +450,25 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void deleteAllTracks() {
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null);
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
null, null);
context.getContentResolver().delete(
WaypointsColumns.CONTENT_URI, null, null);
}
@Override
public void deleteTrack(long trackId) {
Track track = getTrack(trackId);
if (track != null) {
contentResolver.delete(TrackPointsColumns.CONTENT_URI,
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
null);
}
contentResolver.delete(WaypointsColumns.CONTENT_URI,
context.getContentResolver().delete(WaypointsColumns.CONTENT_URI,
WaypointsColumns.TRACKID + "=" + trackId, null);
contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
context.getContentResolver().delete(
TracksColumns.CONTENT_URI, "_id=" + trackId, null);
}
@Override
@@ -488,7 +493,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
"No statistics marker after the deleted one was found.");
}
}
contentResolver.delete(
context.getContentResolver().delete(
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
}
@@ -500,7 +505,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
Cursor cursor = null;
try {
cursor = contentResolver.query(
cursor = context.getContentResolver().query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
selection,
@@ -522,7 +527,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public boolean updateWaypoint(Waypoint waypoint) {
try {
final int rows = contentResolver.update(
final int rows = context.getContentResolver().update(
WaypointsColumns.CONTENT_URI,
createContentValues(waypoint),
"_id=" + waypoint.getId(),
@@ -543,7 +548,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private Location findLocationBy(String select) {
Cursor cursor = null;
try {
cursor = contentResolver.query(
cursor = context.getContentResolver().query(
TrackPointsColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createLocation(cursor);
@@ -567,7 +572,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private Track findTrackBy(String select) {
Cursor cursor = null;
try {
cursor = contentResolver.query(
cursor = context.getContentResolver().query(
TracksColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
@@ -589,7 +594,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Waypoint getFirstWaypoint(long trackId) {
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"trackid=" + trackId,
@@ -611,7 +616,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Waypoint getWaypoint(long waypointId) {
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"_id=" + waypointId,
@@ -634,7 +639,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastLocationId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
TrackPointsColumns.CONTENT_URI,
projection,
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
@@ -658,7 +663,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getFirstWaypointId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
WaypointsColumns.CONTENT_URI,
projection,
"trackid=" + trackId,
@@ -682,7 +687,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastWaypointId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
WaypointsColumns.CONTENT_URI,
projection,
WaypointsColumns.TRACKID + "=" + trackId,
@@ -707,7 +712,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
public Track getLastTrack() {
Cursor cursor = null;
try {
cursor = contentResolver.query(
cursor = context.getContentResolver().query(
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null && cursor.moveToNext()) {
@@ -726,7 +731,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public long getLastTrackId() {
String[] proj = { TracksColumns._ID };
Cursor cursor = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null) {
@@ -766,13 +771,13 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxLocations;
}
return contentResolver.query(
return context.getContentResolver().query(
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@Override
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
int maxWaypoints) {
long maxWaypoints) {
String selection;
if (minWaypointId > 0) {
selection = String.format("%s=%d AND %s>=%d",
@@ -788,7 +793,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
sortOrder += " LIMIT " + maxWaypoints;
}
return contentResolver.query(
return context.getContentResolver().query(
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@@ -797,21 +802,6 @@ 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) {
@@ -842,9 +832,56 @@ 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 = contentResolver.query(
Cursor cursor = context.getContentResolver().query(
TracksColumns.CONTENT_URI, null, selection, null, "_id");
return cursor;
}
@@ -852,14 +889,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Uri insertTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
return contentResolver.insert(TracksColumns.CONTENT_URI,
return context.getContentResolver().insert(TracksColumns.CONTENT_URI,
createContentValues(track));
}
@Override
public Uri insertTrackPoint(Location location, long trackId) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI,
createContentValues(location, trackId));
}
@@ -872,14 +909,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values[i] = createContentValues(locations[i], trackId);
}
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
return context.getContentResolver().bulkInsert(TrackPointsColumns.CONTENT_URI, values);
}
@Override
public Uri insertWaypoint(Waypoint waypoint) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
waypoint.setId(-1);
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI,
createContentValues(waypoint));
}
@@ -888,7 +925,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
Cursor cursor = null;
try {
final String[] projection = { TracksColumns._ID };
cursor = contentResolver.query(
cursor = context.getContentResolver().query(
TracksColumns.CONTENT_URI,
projection,
TracksColumns._ID + "=" + id/*selection*/,
@@ -908,7 +945,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void updateTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
contentResolver.update(TracksColumns.CONTENT_URI,
context.getContentResolver().update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
}
@@ -0,0 +1,133 @@
/*
* 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;
}
}
@@ -15,8 +15,6 @@
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
@@ -44,14 +42,4 @@ public interface TrackPointsColumns extends BaseColumns {
public static final String TIME = "time";
public static final String ACCURACY = "accuracy";
public static final String SPEED = "speed";
/** Columns that go into the backup. */
public static final String[] BACKUP_COLUMNS =
{ _ID, TRACKID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
ACCURACY, SPEED };
/** Types of the columns that go into the backup. */
public static final byte[] BACKUP_COLUMN_TYPES =
{ LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID };
}
@@ -15,8 +15,6 @@
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
@@ -61,16 +59,4 @@ public interface TracksColumns extends BaseColumns {
public static final String MINLON = "minlon";
public static final String MAXLON = "maxlon";
public static final String MAPID = "mapid";
public static final String[] BACKUP_COLUMNS = {
_ID, NAME, DESCRIPTION, CATEGORY, STARTID, STOPID, STARTTIME, STOPTIME,
NUMPOINTS, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED, AVGMOVINGSPEED,
MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN, MINGRADE, MAXGRADE,
MINLAT, MAXLAT, MINLON, MAXLON };
public static final byte[] BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
}
@@ -15,8 +15,6 @@
*/
package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
@@ -68,19 +66,4 @@ public interface WaypointsColumns extends BaseColumns {
public static final String ELEVATIONGAIN = "elevationgain";
public static final String MINGRADE = "mingrade";
public static final String MAXGRADE = "maxgrade";
public static final String[] BACKUP_COLUMNS = {
_ID, TRACKID, NAME, DESCRIPTION, CATEGORY, ICON, TYPE, LENGTH, DURATION,
STARTTIME, STARTID, STOPID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
ACCURACY, SPEED, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED,
AVGMOVINGSPEED, MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN,
MINGRADE, MAXGRADE };
public static final byte[] BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
STRING_TYPE_ID, STRING_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID };
}
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import android.location.Location;
import android.os.Environment;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -46,7 +47,7 @@ import java.util.TimeZone;
public class CsvTrackWriter implements TrackFormatWriter {
static final SimpleDateFormat TIMESTAMP_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
{
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -55,6 +56,13 @@ public class CsvTrackWriter implements TrackFormatWriter {
private PrintWriter pw;
private Track track;
@Override
public String getDefaultDirectory() {
return Environment.getExternalStorageDirectory()
+ System.getProperty("file.separator")
+ getExtension();
}
@Override
public String getExtension() {
return TrackFileFormat.CSV.getExtension();
@@ -20,7 +20,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import android.location.Location;
import android.location.LocationManager;
@@ -29,8 +28,11 @@ import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.SimpleTimeZone;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
@@ -52,6 +54,17 @@ import org.xml.sax.helpers.DefaultHandler;
*/
public class GpxImporter extends DefaultHandler {
/*
* Different date formats used in GPX files
*/
static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssZ");
static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss'Z'");
static final SimpleDateFormat DATE_FORMAT3 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ");
static final SimpleTimeZone UTC_TIMEZONE = new SimpleTimeZone(0, "UTC");
/*
* GPX-XML tag names and attributes.
*/
@@ -154,6 +167,7 @@ public class GpxImporter extends DefaultHandler {
* Reads GPS tracks from a GPX file and writes tracks and their coordinates to
* the database.
*
* @param tracks a list of tracks
* @param is a input steam with gpx-xml data
* @return long[] array of track ids written in the database
* @throws SAXException a parsing error
@@ -192,7 +206,7 @@ public class GpxImporter extends DefaultHandler {
this.providerUtils = providerUtils;
tracksWritten = new ArrayList<Long>();
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String newContent = new String(ch, start, length);
@@ -434,16 +448,7 @@ public class GpxImporter extends DefaultHandler {
private void onTimeElementEnd() throws SAXException {
if (location == null) { return; }
// Parse the time
long time;
try {
time = StringUtils.parseXmlDateTime(content.trim());
} catch (IllegalArgumentException e) {
String msg = createErrorMessage("Unable to parse time: " + content);
throw new SAXException(msg, e);
}
// Calculate derived attributes from previous point
long time = parseTimeForAllFormats(content);
if (lastSegmentLocation != null) {
long timeDifference = time - lastSegmentLocation.getTime();
@@ -463,12 +468,11 @@ public class GpxImporter extends DefaultHandler {
location.setBearing(lastSegmentLocation.bearingTo(location));
}
// Fill in the time
location.setTime(time);
// initialize start time with time of first track point
if (statsBuilder == null) {
statsBuilder = new TripStatisticsBuilder(time);
statsBuilder = new TripStatisticsBuilder();
statsBuilder.resumeAt(time);
}
}
@@ -502,6 +506,46 @@ public class GpxImporter extends DefaultHandler {
return result;
}
/**
* Parse time trying different formats used in GPX files.
*
* @param timeContents string with time information
* @return time as long
* @throws SAXException on time parsing errors
*/
private long parseTimeForAllFormats(String timeContents) throws SAXException {
long time;
timeContents = timeContents.trim();
// 1st try with time zone at end a la "+0000"
time = parseTime(timeContents, DATE_FORMAT1);
if (time > -1) { return time; }
// if that fails, try with a literal "Z" at the end
// (this is not according to xml standard, but some gpx files are like
// that):
time = parseTime(timeContents, DATE_FORMAT2);
if (time > -1) { return time; }
// some gpx timestamps have 3 additional digits at the end.
time = parseTime(timeContents, DATE_FORMAT3);
if (time > -1) { return time; }
// everything failed - abort the import
String msg = createErrorMessage("Invalid time format: " + timeContents);
throw new SAXException(msg);
}
private long parseTime(String timeContents, SimpleDateFormat formatParam) {
SimpleDateFormat format = formatParam;
try {
format.setTimeZone(UTC_TIMEZONE);
return format.parse(timeContents).getTime();
} catch (ParseException ex) {
return -1;
}
}
/**
* Returns the ID of the last point inserted into the database.
*/
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import android.location.Location;
import android.os.Environment;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -37,7 +38,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
static final SimpleDateFormat TIMESTAMP_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
{
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -50,6 +51,13 @@ public class GpxTrackWriter implements TrackFormatWriter {
this.pw = new PrintWriter(out);
}
@Override
public String getDefaultDirectory() {
return Environment.getExternalStorageDirectory()
+ System.getProperty("file.separator")
+ getExtension();
}
@Override
public String getExtension() {
return TrackFileFormat.GPX.getExtension();
@@ -137,7 +145,6 @@ public class GpxTrackWriter implements TrackFormatWriter {
@Override
public void writeWaypoint(Waypoint waypoint) {
if (pw != null) {
// TODO: The gpx spec says waypoints should come *before* tracks
Location l = waypoint.getLocation();
if (l != null) {
pw.println("<wpt lat=\"" + l.getLatitude() + "\" lon=\""
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.util.StringUtils;
import android.content.Context;
import android.location.Location;
import android.os.Environment;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -57,6 +58,12 @@ public class KmlTrackWriter implements TrackFormatWriter {
this.pw = new PrintWriter(out);
}
@Override
public String getDefaultDirectory() {
return Environment.getExternalStorageDirectory()
+ System.getProperty("file.separator") + getExtension();
}
@Override
public String getExtension() {
return TrackFileFormat.KML.getExtension();
@@ -209,7 +209,7 @@ public class SendToDocs {
androidClient = GDataClientFactory.getGDataClient(activity);
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
wiseWrapper.setClient(gdataClient);
wiseWrapper.setClient(androidClient, 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(docsGdataClient);
writelyWrapper.setClient(androidClient, docsGdataClient);
// First try to find the spreadsheet:
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
File diff suppressed because it is too large Load Diff
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import android.location.Location;
import android.os.Build;
import android.os.Environment;
import java.io.OutputStream;
import java.io.PrintWriter;
@@ -42,7 +43,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
static final SimpleDateFormat TIMESTAMP_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
{
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -64,6 +65,12 @@ public class TcxTrackWriter implements TrackFormatWriter {
}
}
@Override
public String getDefaultDirectory() {
return Environment.getExternalStorageDirectory()
+ System.getProperty("file.separator") + getExtension();
}
@Override
public String getExtension() {
return TrackFileFormat.TCX.getExtension();
@@ -1,73 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.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;
}
}
@@ -53,6 +53,14 @@ public interface TrackFormatWriter {
*/
void prepare(Track track, OutputStream out);
/**
* Determines the default location that will be written to (if no other is
* specified).
*
* @return The absolute path for the the file created, or null on failure
*/
String getDefaultDirectory();
/**
* @return The file extentsion (i.e. gpx, kml, ...)
*/
@@ -18,8 +18,8 @@ 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;
import com.google.android.maps.mytracks.R;
@@ -27,12 +27,14 @@ import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashSet;
/**
* The class which exports tracks to the SD card.
@@ -49,20 +51,39 @@ public class TrackWriter {
private final MyTracksProviderUtils providerUtils;
private final Track track;
private final TrackFormatWriter writer;
private final FileUtils fileUtils;
private Runnable onCompletion = null;
private boolean success = false;
private int errorMessage = -1;
private File directory = null;
private File file = null;
/**
* A set of characters that are prohibited from being in file names.
*/
private static final HashSet<Character> PROHIBITED_CHARACTERS =
new HashSet<Character>();
static {
for (int i = 0; i < 48; i++) {
PROHIBITED_CHARACTERS.add(new Character((char) i));
}
for (int i = 58; i < 65; i++) {
PROHIBITED_CHARACTERS.add(new Character((char) i));
}
for (int i = 91; i < 97; i++) {
PROHIBITED_CHARACTERS.add(new Character((char) i));
}
for (int i = 123; i < 128; i++) {
PROHIBITED_CHARACTERS.add(new Character((char) i));
}
}
public TrackWriter(Context context, MyTracksProviderUtils providerUtils,
Track track, TrackFormatWriter writer) {
this.context = context;
this.providerUtils = providerUtils;
this.track = track;
this.writer = writer;
this.fileUtils = new FileUtils();
}
/**
@@ -128,6 +149,26 @@ public class TrackWriter {
* ===============
*/
/**
* Makes sure the given directory exists, creating it if necessary.
*
* @param dir the directory to ensure exists
* @return True on success
*/
private static boolean ensureExists(File dir) {
if (dir.exists()) {
return true;
}
File parent = dir.getParentFile();
if (parent == null) {
return true;
}
if (!ensureExists(parent)) {
return false;
}
return dir.mkdir();
}
private void finished() {
if (onCompletion != null) {
runOnUiThread(onCompletion);
@@ -154,15 +195,9 @@ public class TrackWriter {
return false;
}
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = fileUtils.buildUniqueFileName(
directory, track.getName(), writer.getExtension());
if (fileName == null) {
Log.e(MyTracksConstants.TAG,
"Unable to get a unique filename for " + fileName);
return false;
}
// Make sure the name will work on FAT
String fileName =
sanitizeName(track.getName()) + "." + writer.getExtension();
Log.i(MyTracksConstants.TAG, "Writing track to: " + fileName);
try {
writer.prepare(track, newOutputStream(fileName));
@@ -179,18 +214,17 @@ public class TrackWriter {
*/
protected boolean canWriteFile() {
if (directory == null) {
String dirName =
fileUtils.buildExternalDirectoryPath(writer.getExtension());
directory = newFile(dirName);
directory = newFile(writer.getDefaultDirectory());
}
if (!fileUtils.isSdCardAvailable()) {
if (!Environment.getExternalStorageState()
.equals(Environment.MEDIA_MOUNTED)) {
Log.i(MyTracksConstants.TAG, "Could not find SD card.");
errorMessage = R.string.io_no_external_storage_found;
return false;
}
if (!fileUtils.ensureDirectoryExists(directory)) {
Log.i(MyTracksConstants.TAG, "Could not create export directory.");
if (!ensureExists(directory)) {
Log.i(MyTracksConstants.TAG, "Could not create GPX directory.");
errorMessage = R.string.io_create_dir_failed;
return false;
}
@@ -216,6 +250,25 @@ public class TrackWriter {
return new File(path);
}
/**
* Normalizes the input string and make sure it is a valid fat32 file name.
*/
static String sanitizeName(String name) {
StringBuilder cleaned = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (!PROHIBITED_CHARACTERS.contains(c)) {
cleaned.append(c);
}
}
// Max = 260
// Boiler plate /[gpx|kml]/ .[gpx|kml] = 8
return (cleaned.length() > 252)
? cleaned.substring(0, 252)
: cleaned.toString();
}
/**
* Writes the waypoints for the given track.
*
@@ -253,8 +306,55 @@ public class TrackWriter {
void writeDocument() {
Log.d(MyTracksConstants.TAG, "Started writing track.");
writer.writeHeader();
// TODO: Fix ordering (in GPX waypoints should come first)
writeLocations();
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);
}
writeWaypoints(track.getId());
writer.writeFooter();
writer.close();
@@ -262,66 +362,4 @@ 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
*/
private static TrackWriter newWriter(Context context,
public static TrackWriter newWriter(Context context,
MyTracksProviderUtils providerUtils,
Track track, TrackFileFormat format) {
TrackFormatWriter writer = format.newFormatWriter(context);
@@ -1,247 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
/**
* Helper which shows a UI for writing or restoring a backup,
* and calls the appropriate handler for actually executing those
* operations.
*
* @author Rodrigo Damazio
*/
public class BackupActivityHelper {
// Since the user sees this format, we use the local timezone
private static final SimpleDateFormat DISPLAY_BACKUP_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final Comparator<Date> REVERSE_DATE_ORDER =
new Comparator<Date>() {
@Override
public int compare(Date s1, Date s2) {
return s2.compareTo(s1);
}
};
private final FileUtils fileUtils;
private final ExternalFileBackup backup;
private final Activity activity;
public BackupActivityHelper(Activity activity) {
this.activity = activity;
this.fileUtils = new FileUtils();
this.backup = new ExternalFileBackup(activity, fileUtils);
}
/**
* Writes a full backup to the default file.
* This shows the results to the user.
*/
public void writeBackup() {
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.io_no_external_storage_found);
return;
}
if (!backup.isBackupsDirectoryAvailable(true)) {
showToast(R.string.io_create_dir_failed);
return;
}
final ProgressDialog progressDialog = ProgressDialog.show(
activity,
activity.getString(R.string.progress_title),
activity.getString(R.string.backup_write_progress_message),
true);
// Do the writing in another thread
new Thread() {
@Override
public void run() {
try {
backup.writeToDefaultFile();
showToast(R.string.io_write_finished);
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Failed to write backup", e);
showToast(R.string.io_write_failed);
} finally {
dismissDialog(progressDialog);
}
}
}.start();
}
/**
* Restores a full backup from the SD card.
* The user will be given a choice of which backup to restore as well as a
* confirmation dialog.
*/
public void restoreBackup() {
// Get the list of existing backups
if (!fileUtils.isSdCardAvailable()) {
showToast(R.string.io_no_external_storage_found);
return;
}
if (!backup.isBackupsDirectoryAvailable(false)) {
showToast(R.string.no_backups);
return;
}
final Date[] backupDates = backup.getAvailableBackups();
if (backupDates == null || backupDates.length == 0) {
showToast(R.string.no_backups);
return;
}
Arrays.sort(backupDates, REVERSE_DATE_ORDER);
// Show a confirmation dialog
Builder confirmationDialogBuilder = new AlertDialog.Builder(activity);
confirmationDialogBuilder.setMessage(R.string.restore_overwrites_warning);
confirmationDialogBuilder.setCancelable(true);
confirmationDialogBuilder.setPositiveButton(android.R.string.yes,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pickBackupForRestore(backupDates);
}
});
confirmationDialogBuilder.setNegativeButton(android.R.string.no, null);
confirmationDialogBuilder.create().show();
}
/**
* Shows a backup list for the user to pick, then restores it.
*
* @param backupDir the backup directory
* @param backupDates the list of available backup files
*/
private void pickBackupForRestore(final Date[] backupDates) {
if (backupDates.length == 1) {
// Only one choice, don't bother showing the list
restoreFromDateAsync(backupDates[0]);
return;
}
// Make a user-visible version of the backup filenames
final String backupDateStrs[] = new String[backupDates.length];
for (int i = 0; i < backupDates.length; i++) {
backupDateStrs[i] = DISPLAY_BACKUP_FORMAT.format(backupDates[i]);
}
// Show a dialog for the user to pick which backup to restore
Builder dialogBuilder = new AlertDialog.Builder(activity);
dialogBuilder.setCancelable(true);
dialogBuilder.setTitle(R.string.select_backup_to_restore);
dialogBuilder.setItems(backupDateStrs, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// User picked to restore this one
restoreFromDateAsync(backupDates[which]);
}
});
dialogBuilder.create().show();
}
/**
* Shows a progress dialog, then starts restoring the backup osynchronously.
*
* @param inputFile the file to restore from
*/
private void restoreFromDateAsync(final Date date) {
// Show a progress dialog
final ProgressDialog progressDialog = ProgressDialog.show(
activity,
activity.getString(R.string.progress_title),
activity.getString(R.string.backup_import_progress_message),
true);
// Do the actual importing in another thread (don't block the UI)
new Thread() {
@Override
public void run() {
try {
backup.restoreFromDate(date);
showToast(R.string.io_read_finished);
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Failed to restore backup", e);
showToast(R.string.io_read_failed);
} finally {
dismissDialog(progressDialog);
// Data may have been restored, "reboot" the app to catch it
restartApplication();
}
}
}.start();
}
/**
* Restarts My Tracks completely.
* This forces any modified data to be re-read.
*/
private void restartApplication() {
Intent intent = new Intent(activity, MyTracks.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivity(intent);
}
/**
* Shows a toast with the given contents.
*/
private void showToast(final int resId) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity, resId, Toast.LENGTH_LONG).show();
}
});
}
/**
* Safely dismisses the given dialog.
*/
private void dismissDialog(final Dialog dialog) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
dialog.dismiss();
}
});
}
}
@@ -1,77 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
/**
* Shared preferences listener which notifies the backup system about new data
* being available for backup.
* This class is API-version-safe and will provide a dummy implementation if
* the device doesn't support backup services.
*
* @author Rodrigo Damazio
*/
public abstract class BackupPreferencesListener
implements OnSharedPreferenceChangeListener {
/**
* Real implementation of the listener, which calls the {@link BackupManager}.
*/
private static class BackupPreferencesListenerImpl
extends BackupPreferencesListener {
private final BackupManager backupManager;
public BackupPreferencesListenerImpl(Context context) {
this.backupManager = new BackupManager(context);
}
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
backupManager.dataChanged();
}
}
/**
* Dummy implementation of the listener which does nothing.
*/
private static class DummyBackupPreferencesListener
extends BackupPreferencesListener {
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
// Do nothing
}
}
/**
* Creates and returns a proper instance of the listener for this device.
*/
public static BackupPreferencesListener create(
Context context, ApiFeatures apiFeatures) {
if (apiFeatures.hasBackup()) {
return new BackupPreferencesListenerImpl(context);
} else {
return new DummyBackupPreferencesListener();
}
}
}
@@ -1,236 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.content.ContentTypeIds;
import android.database.Cursor;
import android.database.MergeCursor;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Database dumper which is able to write only part of the database
* according to some query.
*
* This dumper is symmetrical to {@link DatabaseImporter}.
*
* @author Rodrigo Damazio
*/
class DatabaseDumper {
/** The names of the columns being dumped. */
private final String[] columnNames;
/** The types of the columns being dumped. */
private final byte[] columnTypes;
/** Whether to output null fields. */
private final boolean outputNullFields;
// Temporary state
private int[] columnIndices;
private boolean[] hasFields;
public DatabaseDumper(String[] columnNames, byte[] columnTypes,
boolean outputNullFields) {
if (columnNames.length != columnTypes.length) {
throw new IllegalArgumentException("Names don't match types");
}
this.columnNames = columnNames;
this.columnTypes = columnTypes;
this.outputNullFields = outputNullFields;
}
/**
* Writes the header plus all rows that can be read from the given cursor.
* This assumes the cursor will have the same column and column indices on
* every row (and thus may not work with a {@link MergeCursor}).
*/
public void writeAllRows(Cursor cursor, DataOutputStream writer)
throws IOException {
writeHeaders(cursor, cursor.getCount(), writer);
if (!cursor.moveToFirst()) {
return;
}
do {
writeOneRow(cursor, writer);
} while (cursor.moveToNext());
}
/**
* Writes just the headers for the data that will come from the given cursor.
* The headers include column information and the number of rows that will be
* written.
*
* @param cursor the cursor to get columns from
* @param numRows the number of rows that will be later written
* @throws IOException if there are errors while writing
*/
public void writeHeaders(Cursor cursor, int numRows, DataOutputStream writer)
throws IOException {
initializeCachedValues(cursor);
writeQueryMetadata(cursor, numRows, writer);
}
/**
* Writes the current row from the cursor. The cursor is not advanced.
* This must be called after {@link #writeHeaders}.
*
* @param cursor the cursor to write data from
* @throws IOException if there are any errors while writing
*/
public void writeOneRow(Cursor cursor, DataOutputStream writer)
throws IOException {
if (columnIndices == null) {
throw new IllegalStateException(
"Cannot write rows before writing the header");
}
if (columnIndices.length > Long.SIZE) {
throw new IllegalArgumentException("Too many fields");
}
// Build a bitmap of which fields are present
long fields = 0;
for (int i = 0; i < columnIndices.length; i++) {
hasFields[i] = !cursor.isNull(columnIndices[i]);
fields |= (hasFields[i] ? 1 : 0) << i;
}
writer.writeLong(fields);
// Actually write the present fields
for (int i = 0; i < columnIndices.length; i++) {
if (hasFields[i]) {
writeCell(columnIndices[i], columnTypes[i], cursor, writer);
} else if (outputNullFields) {
writeDummyCell(columnTypes[i], writer);
}
}
}
/**
* Initializes the column indices and other temporary state for reading from
* the given cursor.
*/
private void initializeCachedValues(Cursor cursor) {
// These indices are constant for every row (unless we're fed a MergeCursor)
if (cursor instanceof MergeCursor) {
throw new IllegalArgumentException("Cannot use a MergeCursor");
}
columnIndices = new int[columnNames.length];
for (int i = 0; i < columnNames.length; i++) {
String columnName = columnNames[i];
columnIndices[i] = cursor.getColumnIndexOrThrow(columnName);
}
hasFields = new boolean[columnIndices.length];
}
/**
* Writes metadata about the query to be dumped.
*
* @param cursor the cursor that the data will come from
* @param numRows the number of rows that will be dumped
* @throws IOException if there are any errors while writing
*/
private void writeQueryMetadata(
Cursor cursor, int numRows, DataOutputStream writer) throws IOException {
// Write column data
writer.writeInt(columnNames.length);
for (int i = 0; i < columnNames.length; i++) {
String columnName = columnNames[i];
byte columnType = columnTypes[i];
writer.writeUTF(columnName);
writer.writeByte(columnType);
}
// Write the number of rows
writer.writeInt(numRows);
}
/**
* Writes a single cell of the database to the output.
*
* @param columnIdx the column index to read from
* @param columnType the type of the column to be read
* @param cursor the cursor to read from
* @throws IOException if there are any errors while writing
*/
private void writeCell(
int columnIdx, byte columnTypeId, Cursor cursor, DataOutputStream writer)
throws IOException {
switch (columnTypeId) {
case ContentTypeIds.LONG_TYPE_ID:
writer.writeLong(cursor.getLong(columnIdx));
return;
case ContentTypeIds.DOUBLE_TYPE_ID:
writer.writeDouble(cursor.getDouble(columnIdx));
return;
case ContentTypeIds.FLOAT_TYPE_ID:
writer.writeFloat(cursor.getFloat(columnIdx));
return;
case ContentTypeIds.BOOLEAN_TYPE_ID:
writer.writeBoolean(cursor.getInt(columnIdx) != 0);
return;
case ContentTypeIds.INT_TYPE_ID:
writer.writeInt(cursor.getInt(columnIdx));
return;
case ContentTypeIds.STRING_TYPE_ID:
writer.writeUTF(cursor.getString(columnIdx));
return;
default:
throw new IllegalArgumentException(
"Type " + columnTypeId + " not supported");
}
}
/**
* Writes a dummy cell value to the output.
*
* @param columnType the type of the value to write
* @throws IOException if there are any errors while writing
*/
private void writeDummyCell(byte columnTypeId, DataOutputStream writer)
throws IOException {
switch (columnTypeId) {
case ContentTypeIds.LONG_TYPE_ID:
writer.writeLong(0L);
return;
case ContentTypeIds.DOUBLE_TYPE_ID:
writer.writeDouble(0.0);
return;
case ContentTypeIds.FLOAT_TYPE_ID:
writer.writeFloat(0.0f);
return;
case ContentTypeIds.BOOLEAN_TYPE_ID:
writer.writeBoolean(false);
return;
case ContentTypeIds.INT_TYPE_ID:
writer.writeInt(0);
return;
case ContentTypeIds.STRING_TYPE_ID:
writer.writeUTF("");
return;
default:
throw new IllegalArgumentException(
"Type " + columnTypeId + " not supported");
}
}
}
@@ -1,180 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.content.ContentTypeIds;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import java.io.DataInputStream;
import java.io.IOException;
/**
* Database importer which reads values written by {@link DatabaseDumper}.
*
* @author Rodrigo Damazio
*/
public class DatabaseImporter {
/** Maximum number of entries in a bulk insertion */
private static final int DEFAULT_BULK_SIZE = 1024;
private final Uri destinationUri;
private final ContentResolver resolver;
private final boolean readNullFields;
private final int bulkSize;
// Metadata read from the reader
private String[] columnNames;
private byte[] columnTypes;
public DatabaseImporter(Uri destinationUri, ContentResolver resolver,
boolean readNullFields) {
this(destinationUri, resolver, readNullFields, DEFAULT_BULK_SIZE);
}
protected DatabaseImporter(Uri destinationUri, ContentResolver resolver,
boolean readNullFields, int bulkSize) {
this.destinationUri = destinationUri;
this.resolver = resolver;
this.readNullFields = readNullFields;
this.bulkSize = bulkSize;
}
/**
* Reads the header which includes metadata about the table being imported.
*
* @throws IOException if there are any problems while reading
*/
private void readHeaders(DataInputStream reader) throws IOException {
int numColumns = reader.readInt();
columnNames = new String[numColumns];
columnTypes = new byte[numColumns];
for (int i = 0; i < numColumns; i++) {
columnNames[i] = reader.readUTF();
columnTypes[i] = reader.readByte();
}
}
/**
* Imports all rows from the reader into the database.
* Insertion is done in bulks for efficiency.
*
* @throws IOException if there are any errors while reading
*/
public void importAllRows(DataInputStream reader) throws IOException {
readHeaders(reader);
ContentValues[] valueBulk = new ContentValues[bulkSize];
int numValues = 0;
int numRows = reader.readInt();
int numColumns = columnNames.length;
// For each row
for (int r = 0; r < numRows; r++) {
if (valueBulk[numValues] == null) {
valueBulk[numValues] = new ContentValues(numColumns);
} else {
// Reuse values objects
valueBulk[numValues].clear();
}
// Read the fields bitmap
long fields = reader.readLong();
for (int c = 0; c < numColumns; c++) {
if ((fields & 1) == 1) {
// Field is present, read into values
readOneCell(columnNames[c], columnTypes[c], valueBulk[numValues],
reader);
} else if (readNullFields) {
// Field not present but still written, read and discard
readOneCell(columnNames[c], columnTypes[c], null, reader);
}
fields >>= 1;
}
numValues++;
// If we have enough values, flush them as a bulk insertion
if (numValues >= bulkSize) {
doBulkInsert(valueBulk);
numValues = 0;
}
}
// Do a final bulk insert with the leftovers
if (numValues > 0) {
ContentValues[] leftovers = new ContentValues[numValues];
System.arraycopy(valueBulk, 0, leftovers, 0, numValues);
doBulkInsert(leftovers);
}
}
protected void doBulkInsert(ContentValues[] values) {
resolver.bulkInsert(destinationUri, values);
}
/**
* Reads a single cell from the reader.
*
* @param name the name of the column to be read
* @param typeId the type ID of the column to be read
* @param values the {@link ContentValues} object to put the read cell value
* in - if null, the value is just discarded
* @throws IOException if there are any problems while reading
*/
private void readOneCell(String name, byte typeId, ContentValues values,
DataInputStream reader) throws IOException {
switch (typeId) {
case ContentTypeIds.BOOLEAN_TYPE_ID: {
boolean value = reader.readBoolean();
if (values != null) { values.put(name, value); }
return;
}
case ContentTypeIds.LONG_TYPE_ID: {
long value = reader.readLong();
if (values != null) { values.put(name, value); }
return;
}
case ContentTypeIds.DOUBLE_TYPE_ID: {
double value = reader.readDouble();
if (values != null) { values.put(name, value); }
return;
}
case ContentTypeIds.FLOAT_TYPE_ID: {
Float value = reader.readFloat();
if (values != null) { values.put(name, value); }
return;
}
case ContentTypeIds.INT_TYPE_ID: {
int value = reader.readInt();
if (values != null) { values.put(name, value); }
return;
}
case ContentTypeIds.STRING_TYPE_ID: {
String value = reader.readUTF();
if (values != null) { values.put(name, value); }
return;
}
default:
throw new IOException("Read unknown type " + typeId);
}
}
}
@@ -1,263 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.util.FileUtils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.util.Log;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* Handler for writing or reading single-file backups.
*
* @author Rodrigo Damazio
*/
class ExternalFileBackup {
// Filename format - in UTC
private static final SimpleDateFormat BACKUP_FILENAME_FORMAT =
new SimpleDateFormat("'backup-'yyyy-MM-dd_HH-mm-ss'.zip'");
static {
BACKUP_FILENAME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private static final String BACKUPS_SUBDIR = "backups";
private static final int BACKUP_FORMAT_VERSION = 1;
private static final String ZIP_ENTRY_NAME =
"backup.mytracks.v" + BACKUP_FORMAT_VERSION;
private static final int COMPRESSION_LEVEL = 8;
private final Context context;
private final FileUtils fileUtils;
public ExternalFileBackup(Context context, FileUtils fileUtils) {
this.context = context;
this.fileUtils = fileUtils;
}
/**
* Returns whether the backups directory is (or can be made) available.
*
* @param create whether to try creating the directory if it doesn't exist
*/
public boolean isBackupsDirectoryAvailable(boolean create) {
return getBackupsDirectory(create) != null;
}
/**
* Returns the backup directory, or null if not available.
*
* @param create whether to try creating the directory if it doesn't exist
*/
private File getBackupsDirectory(boolean create) {
String dirName = fileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
final File dir = new File(dirName);
Log.d(MyTracksConstants.TAG, "Dir: " + dir.getAbsolutePath());
if (create) {
// Try to create - if that fails, return null
return fileUtils.ensureDirectoryExists(dir) ? dir : null;
} else {
// Return it if it already exists, otherwise return null
return dir.isDirectory() ? dir : null;
}
}
/**
* Returns a list of available backups to be restored.
*/
public Date[] getAvailableBackups() {
File dir = getBackupsDirectory(false);
if (dir == null) { return null; }
String[] fileNames = dir.list();
List<Date> backupDates = new ArrayList<Date>(fileNames.length);
for (int i = 0; i < fileNames.length; i++) {
String fileName = fileNames[i];
try {
backupDates.add(BACKUP_FILENAME_FORMAT.parse(fileName));
} catch (ParseException e) {
// Not a backup file, ignore
}
}
return backupDates.toArray(new Date[backupDates.size()]);
}
/**
* Writes the backup to the default file.
*/
public void writeToDefaultFile() throws IOException {
writeToFile(getFileForDate(new Date()));
}
/**
* Restores the backup from the given date.
*/
public void restoreFromDate(Date when) throws IOException {
restoreFromFile(getFileForDate(when));
}
/**
* Produces the proper file descriptor for the given backup date.
*/
private File getFileForDate(Date when) {
File dir = getBackupsDirectory(false);
String fileName = BACKUP_FILENAME_FORMAT.format(when);
File file = new File(dir, fileName);
return file;
}
/**
* Synchronously writes a backup to the given file.
*/
private void writeToFile(File outputFile) throws IOException {
Log.d(MyTracksConstants.TAG,
"Writing backup to file " + outputFile.getAbsolutePath());
// Create all the auxiliary classes that will do the writing
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
DatabaseDumper trackDumper = new DatabaseDumper(
TracksColumns.BACKUP_COLUMNS,
TracksColumns.BACKUP_COLUMN_TYPES,
false);
DatabaseDumper waypointDumper = new DatabaseDumper(
WaypointsColumns.BACKUP_COLUMNS,
WaypointsColumns.BACKUP_COLUMN_TYPES,
false);
DatabaseDumper pointDumper = new DatabaseDumper(
TrackPointsColumns.BACKUP_COLUMNS,
TrackPointsColumns.BACKUP_COLUMN_TYPES,
false);
// Open the target for writing
FileOutputStream outputStream = new FileOutputStream(outputFile);
ZipOutputStream compressedStream = new ZipOutputStream(outputStream);
compressedStream.setLevel(COMPRESSION_LEVEL);
compressedStream.putNextEntry(new ZipEntry(ZIP_ENTRY_NAME));
DataOutputStream outWriter = new DataOutputStream(compressedStream);
try {
// Dump the entire contents of each table
ContentResolver contentResolver = context.getContentResolver();
Cursor tracksCursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, null, null, null);
try {
trackDumper.writeAllRows(tracksCursor, outWriter);
} finally {
tracksCursor.close();
}
Cursor waypointsCursor = contentResolver.query(
WaypointsColumns.CONTENT_URI, null, null, null, null);
try {
waypointDumper.writeAllRows(waypointsCursor, outWriter);
} finally {
waypointsCursor.close();
}
Cursor pointsCursor = contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, null, null, null);
try {
pointDumper.writeAllRows(pointsCursor, outWriter);
} finally {
pointsCursor.close();
}
// Dump preferences
SharedPreferences preferences =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
preferencesHelper.exportPreferences(preferences, outWriter);
} catch (IOException e) {
// We tried to delete the partially created file, but do nothing
// if that also fails.
outputFile.delete();
throw e;
} finally {
compressedStream.closeEntry();
compressedStream.close();
}
}
/**
* Synchronously restores the backup from the given file.
*/
private void restoreFromFile(File inputFile) throws IOException {
Log.d(MyTracksConstants.TAG,
"Restoring from file " + inputFile.getAbsolutePath());
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
ContentResolver resolver = context.getContentResolver();
DatabaseImporter trackImporter =
new DatabaseImporter(TracksColumns.CONTENT_URI, resolver, false);
DatabaseImporter waypointImporter =
new DatabaseImporter(WaypointsColumns.CONTENT_URI, resolver, false);
DatabaseImporter pointImporter =
new DatabaseImporter(TrackPointsColumns.CONTENT_URI, resolver, false);
ZipFile zipFile = new ZipFile(inputFile, ZipFile.OPEN_READ);
ZipEntry zipEntry = zipFile.getEntry(ZIP_ENTRY_NAME);
if (zipEntry == null) {
throw new IOException("Invalid backup ZIP file");
}
InputStream compressedStream = zipFile.getInputStream(zipEntry);
DataInputStream reader = new DataInputStream(compressedStream);
try {
// Delete all previous contents of the tables and preferences.
resolver.delete(TracksColumns.CONTENT_URI, null, null);
resolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
resolver.delete(WaypointsColumns.CONTENT_URI, null, null);
// Import the new contents of each table
trackImporter.importAllRows(reader);
waypointImporter.importAllRows(reader);
pointImporter.importAllRows(reader);
// Restore preferences
SharedPreferences preferences =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
preferencesHelper.importPreferences(reader, preferences);
} finally {
compressedStream.close();
zipFile.close();
}
}
}
@@ -1,99 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.IOException;
/**
* Backup agent used to backup and restore all preferences.
* We use a regular {@link BackupAgent} instead of the convenient helpers in
* order to be future-proof (assuming we'll want to back up tracks later).
*
* @author Rodrigo Damazio
*/
public class MyTracksBackupAgent extends BackupAgent {
private static final String PREFERENCES_ENTITY = "prefs";
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
Log.i(MyTracksConstants.TAG, "Performing backup");
SharedPreferences preferences = this.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
backupPreferences(data, preferences);
Log.i(MyTracksConstants.TAG, "Backup complete");
}
private void backupPreferences(BackupDataOutput data,
SharedPreferences preferences) throws IOException {
PreferenceBackupHelper preferenceDumper = createPreferenceBackupHelper();
byte[] dumpedContents = preferenceDumper.exportPreferences(preferences);
data.writeEntityHeader(PREFERENCES_ENTITY, dumpedContents.length);
data.writeEntityData(dumpedContents, dumpedContents.length);
}
protected PreferenceBackupHelper createPreferenceBackupHelper() {
return new PreferenceBackupHelper();
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
Log.i(MyTracksConstants.TAG, "Restoring from backup");
while (data.readNextHeader()) {
String key = data.getKey();
Log.d(MyTracksConstants.TAG, "Restoring entity " + key);
if (key.equals(PREFERENCES_ENTITY)) {
restorePreferences(data);
} else {
Log.e(MyTracksConstants.TAG, "Found unknown backup entity: " + key);
data.skipEntityData();
}
}
Log.i(MyTracksConstants.TAG, "Done restoring from backup");
}
/**
* Restores all preferences from the backup.
*
* @param data the backup data to read from
* @throws IOException if there are any errors while reading
*/
private void restorePreferences(BackupDataInput data) throws IOException {
int dataSize = data.getDataSize();
byte[] dataBuffer = new byte[dataSize];
int read = data.readEntityData(dataBuffer, 0, dataSize);
if (read != dataSize) {
throw new IOException("Failed to read all the preferences data");
}
SharedPreferences preferences = this.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
PreferenceBackupHelper importer = createPreferenceBackupHelper();
importer.importPreferences(dataBuffer, preferences);
}
}
@@ -1,176 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.backup;
import com.google.android.apps.mytracks.content.ContentTypeIds;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;
/**
* Helper for backing up and restoring shared preferences.
*
* @author Rodrigo Damazio
*/
class PreferenceBackupHelper {
private static final int BUFFER_SIZE = 2048;
/**
* Exports all shared preferences from the given object as a byte array.
*
* @param preferences the preferences to export
* @return the corresponding byte array
* @throws IOException if there are any errors while writing to the byte array
*/
public byte[] exportPreferences(SharedPreferences preferences)
throws IOException {
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(BUFFER_SIZE);
DataOutputStream outWriter = new DataOutputStream(bufStream);
exportPreferences(preferences, outWriter);
return bufStream.toByteArray();
}
/**
* Exports all shared preferences from the given object into the given output
* stream.
*
* @param preferences the preferences to export
* @param outWriter the stream to write them to
* @throws IOException if there are any errors while writing the output
*/
public void exportPreferences(
SharedPreferences preferences,
DataOutputStream outWriter) throws IOException {
Map<String, ?> values = preferences.getAll();
outWriter.writeInt(values.size());
for (Map.Entry<String, ?> entry : values.entrySet()) {
writePreference(entry.getKey(), entry.getValue(), outWriter);
}
outWriter.flush();
}
/**
* Imports all preferences from the given byte array.
*
* @param data the byte array to read preferences from
* @param preferences the shared preferences to edit
* @return whether the preference change was successful
* @throws IOException if there are any errors while reading
*/
public boolean importPreferences(byte[] data, SharedPreferences preferences)
throws IOException {
ByteArrayInputStream bufStream = new ByteArrayInputStream(data);
DataInputStream reader = new DataInputStream(bufStream);
return importPreferences(reader, preferences);
}
/**
* Imports all preferences from the given stream.
*
* @param reader the stream to read from
* @param preferences the shared preferences to edit
* @return whether the preference change was successful
* @throws IOException if there are any errors while reading
*/
public boolean importPreferences(DataInputStream reader,
SharedPreferences preferences) throws IOException {
Editor editor = preferences.edit();
editor.clear();
int numPreferences = reader.readInt();
for (int i = 0; i < numPreferences; i++) {
String name = reader.readUTF();
byte typeId = reader.readByte();
readAndSetPreference(name, typeId, reader, editor);
}
return editor.commit();
}
/**
* Reads a single preference and sets it into the given editor.
*
* @param name the name of the preference to read
* @param typeId the type ID of the preference to read
* @param reader the reader to read from
* @param editor the editor to set the preference in
* @throws IOException if there are errors while reading
*/
private void readAndSetPreference(String name, byte typeId,
DataInputStream reader, Editor editor) throws IOException {
switch (typeId) {
case ContentTypeIds.BOOLEAN_TYPE_ID:
editor.putBoolean(name, reader.readBoolean());
return;
case ContentTypeIds.LONG_TYPE_ID:
editor.putLong(name, reader.readLong());
return;
case ContentTypeIds.FLOAT_TYPE_ID:
editor.putFloat(name, reader.readFloat());
return;
case ContentTypeIds.INT_TYPE_ID:
editor.putInt(name, reader.readInt());
return;
case ContentTypeIds.STRING_TYPE_ID:
editor.putString(name, reader.readUTF());
return;
}
}
/**
* Writes a single preference.
*
* @param name the name of the preference to write
* @param value the correctly-typed value of the preference
* @param writer the writer to write to
* @throws IOException if there are errors while writing
*/
private void writePreference(String name, Object value, DataOutputStream writer)
throws IOException {
writer.writeUTF(name);
if (value instanceof Boolean) {
writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID);
writer.writeBoolean((Boolean) value);
} else if (value instanceof Integer) {
writer.writeByte(ContentTypeIds.INT_TYPE_ID);
writer.writeInt((Integer) value);
} else if (value instanceof Long) {
writer.writeByte(ContentTypeIds.LONG_TYPE_ID);
writer.writeLong((Long) value);
} else if (value instanceof Float) {
writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID);
writer.writeFloat((Float) value);
} else if (value instanceof String) {
writer.writeByte(ContentTypeIds.STRING_TYPE_ID);
writer.writeUTF((String) value);
} else {
throw new IllegalArgumentException(
"Type " + value.getClass().getName() + " not supported");
}
}
}
@@ -17,6 +17,7 @@ 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;
@@ -27,10 +28,6 @@ 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;
/**
@@ -86,64 +83,44 @@ 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(GDataServiceClient gdataServiceClient) {
public void setClient(GDataClient androidGDataClient,
GDataServiceClient gdataServiceClient) {
this.androidGDataClient = androidGDataClient;
this.gdataServiceClient = gdataServiceClient;
}
public boolean runAuthenticatedFunction(
final AuthenticatedFunction function) {
return runCommon(function, null);
return runOne(function, null);
}
public boolean runQuery(final QueryFunction query) {
return runCommon(null, query);
return runOne(null, query);
}
/**
* Runs an arbitrary piece of code.
*/
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,
private boolean runOne(final AuthenticatedFunction function,
final QueryFunction query) {
try {
if (function != null) {
@@ -151,14 +128,11 @@ public class GDataWrapper {
} else if (query != null) {
query.query(gdataServiceClient);
} else {
throw new IllegalArgumentException(
"invalid invocation of runOne; one of function/query " +
"must be non-null");
return false;
}
errorType = ERROR_NO_ERROR;
errorMessage = null;
return true;
} catch (AuthenticationException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorType = ERROR_AUTH;
@@ -192,42 +166,32 @@ public class GDataWrapper {
errorType = ERROR_CONFLICT;
errorMessage = e.getMessage();
}
}
/**
* 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);
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);
}
return false;
}
@@ -239,10 +203,21 @@ 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;
}
@@ -1,65 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.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,4 +84,13 @@ 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 statistics.
* This class will periodically announce the user's trip statitics.
*
* @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;
private Timer timer = new Timer();
private final PeriodicTask task;
public PeriodicTaskExecuter(PeriodicTask task,
TrackRecordingService service) {
@@ -52,19 +52,8 @@ public class PeriodicTaskExecuter {
* @param interval The interval in milliseconds
*/
public void scheduleTask(long interval) {
// 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.cancel();
timer.purge();
timer = new Timer();
if (interval <= 0) {
return;
@@ -72,14 +61,12 @@ public class PeriodicTaskExecuter {
long now = System.currentTimeMillis();
long next = service.getTripStatistics().getStartTime();
if (next < now) {
next = now + interval - ((now - next) % interval);
}
while (next < now) next += interval;
Date start = new Date(next);
Log.i(MyTracksConstants.TAG,
task.getClass().getSimpleName() + " scheduled to start at " + start
+ " every " + interval + " milliseconds.");
"StatusAnnouncer scheduled to start at " + start + " every "
+ interval + " milliseconds.");
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
}
@@ -87,14 +74,10 @@ public class PeriodicTaskExecuter {
* Cleans up this object.
*/
public void shutdown() {
Log.i(MyTracksConstants.TAG,
task.getClass().getSimpleName() + " shutting down.");
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
task.shutdown();
}
timer.cancel();
timer.purge();
timer = null;
task.shutdown();
}
/**
@@ -15,104 +15,82 @@
*/
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 implements OnSharedPreferenceChangeListener {
public class PreferenceManager {
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;
private final String minRecordingIntervalKey;
private final String minRequiredAccuracyKey;
private final String recordingTrackKey;
private final String signalSamplingFrequencyKey;
private final String splitFrequencyKey;
private String announcement_frequency_key;
private String max_recording_distance_key;
private String metric_units_key;
private String min_recording_distance_key;
private String min_recording_interval_key;
private String min_required_accuracy_key;
private String signal_sampling_frequency_key;
private String split_frequency_key;
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 =
service.getString(R.string.metric_units_key);
minRecordingDistanceKey =
announcement_frequency_key =
service.getString(R.string.announcement_frequency_key);
max_recording_distance_key =
service.getString(R.string.max_recording_distance_key);
metric_units_key =
service.getString(R.string.metric_units_key);
min_recording_distance_key =
service.getString(R.string.min_recording_distance_key);
minRecordingIntervalKey =
service.getString(R.string.min_recording_interval_key);
minRequiredAccuracyKey =
service.getString(R.string.min_required_accuracy_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);
min_recording_interval_key =
service.getString(R.string.min_recording_interval_key);
min_required_accuracy_key =
service.getString(R.string.min_required_accuracy_key);
split_frequency_key =
service.getString(R.string.split_frequency_key);
signal_sampling_frequency_key =
service.getString(R.string.signal_sampling_frequency_key);
}
/**
* 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)
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (service == null) {
public void onSharedPreferenceChanged(String key) {
SharedPreferences sharedPreferences =
service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (sharedPreferences == null) {
Log.w(MyTracksConstants.TAG,
"onSharedPreferenceChanged: a preference change (key = " + key
+ ") after a call to shutdown()");
"TrackRecordingService: Couldn't get shared preferences.");
return;
}
if (key == null || key.equals(minRecordingDistanceKey)) {
if (key == null || key.equals(min_recording_distance_key)) {
service.setMinRecordingDistance(
sharedPreferences.getInt(
minRecordingDistanceKey,
min_recording_distance_key,
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE));
Log.d(MyTracksConstants.TAG,
"TrackRecordingService: minRecordingDistance = "
+ service.getMinRecordingDistance());
}
if (key == null || key.equals(maxRecordingDistanceKey)) {
service.setMaxRecordingDistance(sharedPreferences.getInt(
maxRecordingDistanceKey,
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE));
if (key == null || key.equals(max_recording_distance_key)) {
service.setMaxRecordingDistance(
sharedPreferences.getInt(
max_recording_distance_key,
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE));
}
if (key == null || key.equals(minRecordingIntervalKey)) {
if (key == null || key.equals(min_recording_interval_key)) {
int minRecordingInterval = sharedPreferences.getInt(
minRecordingIntervalKey,
min_recording_interval_key,
MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL);
switch (minRecordingInterval) {
case -2:
@@ -136,59 +114,29 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
new AbsoluteLocationListenerPolicy(minRecordingInterval * 1000));
}
}
if (key == null || key.equals(minRequiredAccuracyKey)) {
service.setMinRequiredAccuracy(sharedPreferences.getInt(
minRequiredAccuracyKey,
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY));
if (key == null || key.equals(min_required_accuracy_key)) {
service.setMinRequiredAccuracy(
sharedPreferences.getInt(
min_required_accuracy_key,
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY));
}
if (key == null || key.equals(announcementFrequencyKey)) {
if (key == null || key.equals(announcement_frequency_key)) {
service.setAnnouncementFrequency(
sharedPreferences.getInt(announcementFrequencyKey, -1));
sharedPreferences.getInt(announcement_frequency_key,
-1));
}
if (key == null || key.equals(autoResumeTrackTimeoutKey)) {
service.setAutoResumeTrackTimeout(sharedPreferences.getInt(
autoResumeTrackTimeoutKey,
MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT));
}
if (key == null || key.equals(recordingTrackKey)) {
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)) {
if (key == null || key.equals(split_frequency_key)) {
service.getSplitManager().setSplitFrequency(
sharedPreferences.getInt(splitFrequencyKey, 0));
sharedPreferences.getInt(split_frequency_key, 0));
}
if (key == null || key.equals(signalSamplingFrequencyKey)) {
if (key == null || key.equals(signal_sampling_frequency_key)) {
service.getSignalManager().setFrequency(
sharedPreferences.getInt(signalSamplingFrequencyKey, -1), service);
sharedPreferences.getInt(
signal_sampling_frequency_key, -1), service);
}
if (key == null || key.equals(metricUnitsKey)) {
service.getSplitManager().setMetricUnits(
sharedPreferences.getBoolean(metricUnitsKey, true));
if (key == null || key.equals(metric_units_key)) {
service.getSplitManager().setMetricUnits(sharedPreferences.getBoolean(
metric_units_key, 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;
}
}
@@ -0,0 +1,62 @@
/*
* 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,8 +44,7 @@ public class SignalStrengthTask extends PhoneStateListener
public SignalStrengthTask(Context c) {
context = c;
}
@Override
public void start() {
manager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
@@ -177,7 +176,6 @@ public class SignalStrengthTask extends PhoneStateListener
}
}
@Override
public void shutdown() {
if (manager != null) {
manager.listen(this, PhoneStateListener.LISTEN_NONE);
@@ -1,48 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.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,15 +73,10 @@ 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,
"SplitManager: Distance splits disabled.");
"SplitManager: Distance splits dissabled.");
return;
}
@@ -125,15 +120,7 @@ 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();

Some files were not shown because too many files have changed in this diff Show More