Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e35e8bcdce | |||
| 5f953aa47f | |||
| 3b3641c69e | |||
| 56ccd518d6 | |||
| 0e119950c2 | |||
| 4be4467fc6 | |||
| 06636b2a28 | |||
| 71a36d1b25 | |||
| 9ba93ff8d2 | |||
| 4059308dd7 | |||
| 9f43dd2db5 | |||
| 28d868985e | |||
| cf1be26da2 | |||
| 1de9b73aa0 | |||
| beddc8e1ea | |||
| e0c34d20a8 | |||
| cc6db5b5cc | |||
| c38df03add | |||
| 63402358d1 | |||
| 3ae1de735e | |||
| a701eb0e4f | |||
| 09caf017a9 | |||
| 32b8b2093d | |||
| c84a4f1aa8 | |||
| d90c0b5431 | |||
| 94f6785f0d | |||
| f01c382807 | |||
| 6d42803dd8 | |||
| 61e908ccb5 | |||
| 7bae8c34a7 | |||
| 3960341640 | |||
| 06e453dd05 | |||
| 361b1cbad4 | |||
| 56a866fca7 | |||
| 49e0d68374 | |||
| c9ffd0b4bb | |||
| 6fd4294391 | |||
| 88b2b85f2a | |||
| 2c1dff6ac8 | |||
| 481b42f563 | |||
| 597c321009 | |||
| 76651f75d8 | |||
| 3d8bae55a1 | |||
| 4b6869e367 | |||
| 477ba7c081 | |||
| 5b6d9725b8 | |||
| 25c3cea115 | |||
| d90601276f | |||
| 4f692bc698 | |||
| d8dd02eef0 | |||
| 8ad65ae233 | |||
| eabbb4569b | |||
| 49888b771d | |||
| 2b1c1cd052 | |||
| 61884e67ec | |||
| 9a2f028843 | |||
| 2b56acf164 | |||
| 98b67dcc28 | |||
| 500dfa3107 | |||
| c4efcfd2db | |||
| 81ee6bbcf6 | |||
| e4e2e76e06 | |||
| f847862e84 | |||
| 427dbeafde | |||
| 99ac85b40e | |||
| 020388baed | |||
| 4ade79de4f | |||
| 870dda9b8b | |||
| 27dd5298f7 | |||
| 6d5d406ce9 | |||
| ffb5e7e6f7 | |||
| 0de101c89f | |||
| bd3a0766e1 | |||
| f536cc8f8a | |||
| 826c0495fc | |||
| 9eac541ce5 |
@@ -3,4 +3,3 @@ syntax:glob
|
|||||||
*/bin
|
*/bin
|
||||||
*/gen/**
|
*/gen/**
|
||||||
.metadata
|
.metadata
|
||||||
MyTracksTest/annotations.log
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="gen"/>
|
||||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
||||||
<classpathentry kind="src" path="src"/>
|
|
||||||
<classpathentry kind="src" path="gen"/>
|
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="output" path="bin"/>
|
||||||
</classpath>
|
</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
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.google.android.maps.mytracks"
|
package="com.google.android.maps.mytracks"
|
||||||
android:installLocation="auto"
|
android:versionCode="17"
|
||||||
android:versionCode="22"
|
android:versionName="1.0.16">
|
||||||
android:versionName="1.0.21">
|
|
||||||
<application android:icon="@drawable/arrow_icon"
|
<application android:icon="@drawable/arrow_icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name">
|
||||||
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
|
|
||||||
<uses-library android:name="com.google.android.maps" />
|
<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"
|
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
|
||||||
|
android:multiprocess="true"
|
||||||
android:authorities="com.google.android.maps.mytracks" />
|
android:authorities="com.google.android.maps.mytracks" />
|
||||||
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
||||||
android:label="My Tracks"
|
android:label="My Tracks"
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale">
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale">
|
||||||
@@ -39,13 +36,16 @@
|
|||||||
|
|
||||||
<activity android:name="com.google.android.apps.mymaps.MyMapsList"
|
<activity android:name="com.google.android.apps.mymaps.MyMapsList"
|
||||||
android:label="@string/map_list_title"
|
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"
|
<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"
|
<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"
|
<activity android:name="com.google.android.apps.mytracks.MyTracksDetails"
|
||||||
android:label="@string/trackdetails_title" />
|
android:label="@string/trackdetails_title" />
|
||||||
@@ -56,34 +56,24 @@
|
|||||||
<activity android:name="com.google.android.apps.mytracks.MyTracksSettings"
|
<activity android:name="com.google.android.apps.mytracks.MyTracksSettings"
|
||||||
android:label="@string/mytracks_settings" />
|
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"
|
<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"
|
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
|
||||||
android:label="@string/elevation_label"/>
|
android:label="@string/elevation_label"/>
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
|
<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"
|
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
||||||
|
android:process=":trackrecord"
|
||||||
android:label="Track Recording Service"
|
android:label="Track Recording Service"
|
||||||
android:icon="@drawable/arrow_320"
|
android:icon="@drawable/arrow_320"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="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>
|
</application>
|
||||||
|
|
||||||
<!-- Permissions to initialize service -->
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
||||||
|
|
||||||
<!-- Permissions for recording tracks and showing maps -->
|
<!-- Permissions for recording tracks and showing maps -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_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.googleapps.permission.GOOGLE_AUTH.writely" />
|
||||||
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
<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"/>
|
<uses-sdk android:minSdkVersion="3"/>
|
||||||
<supports-screens
|
<supports-screens
|
||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
@@ -123,3 +97,4 @@
|
|||||||
android:smallScreens="true"
|
android:smallScreens="true"
|
||||||
android:anyDensity="true" />
|
android:anyDensity="true" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,9 @@
|
|||||||
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
||||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||||
<listEntry value="/MyTracks"/>
|
<listEntry value="/MyTracks"/>
|
||||||
<listEntry value="/MyTracks/AndroidManifest.xml"/>
|
|
||||||
</listAttribute>
|
</listAttribute>
|
||||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||||
<listEntry value="4"/>
|
<listEntry value="4"/>
|
||||||
<listEntry value="1"/>
|
|
||||||
</listAttribute>
|
</listAttribute>
|
||||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
<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">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:state_window_focused="false"
|
<item android:state_window_focused="false"
|
||||||
android:drawable="@drawable/left_arrow_stats" />
|
android:drawable="@drawable/btn_arrow_left_default" />
|
||||||
|
|
||||||
<item android:state_pressed="true"
|
<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>
|
</selector>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
|
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">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:state_window_focused="false"
|
<item android:state_window_focused="false"
|
||||||
android:drawable="@drawable/left_arrow_track" />
|
android:drawable="@drawable/btn_arrow_right_default" />
|
||||||
|
|
||||||
<item android:state_pressed="true"
|
<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>
|
</selector>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
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>
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
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>
|
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
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>
|
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB |
|
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>
|
|
||||||
@@ -21,14 +21,9 @@
|
|||||||
android:orderInCategory="1001"
|
android:orderInCategory="1001"
|
||||||
android:title="@string/settings"/>
|
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"
|
<item android:id="@+id/menu_help"
|
||||||
android:icon="@android:drawable/ic_menu_info_details"
|
android:icon="@android:drawable/ic_menu_info_details"
|
||||||
android:orderInCategory="1003"
|
android:orderInCategory="1002"
|
||||||
android:title="@string/help"/>
|
android:title="@string/help"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_announcement_frequency">Dauer zwischen Ansagen:</string>
|
<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_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">Kleinste Entfernung zwischen Punkten:</string>
|
||||||
<string name="settings_min_recording_distance_summary">Kleinste Entfernung zwischen zwei Positionsaufzeichnungen</string>
|
<string name="settings_min_recording_distance_summary">Kleinste Entfernung zwischen zwei Positionsaufzeichnungen</string>
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_announcement_frequency">Tiempo entre anuncios</string>
|
<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_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">Distancia mínima entre puntos consecutivos</string>
|
||||||
<string name="settings_min_recording_distance_summary">Distancia mínima entre dos grabaciones de posición</string>
|
<string name="settings_min_recording_distance_summary">Distancia mínima entre dos grabaciones de posición</string>
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_announcement_frequency">Fréquence des annonces</string>
|
<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_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">Distance min. entre 2 points</string>
|
||||||
<string name="settings_min_recording_distance_summary">Plus petite distance entre deux enregistrements de votre position</string>
|
<string name="settings_min_recording_distance_summary">Plus petite distance entre deux enregistrements de votre position</string>
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_announcement_frequency">Intervallo fra due annunci:</string>
|
<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_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">Distanza minima fra due punti successivi:</string>
|
||||||
<string name="settings_min_recording_distance_summary">Distanza minima fra due successive registrazioni della posizione</string>
|
<string name="settings_min_recording_distance_summary">Distanza minima fra due successive registrazioni della posizione</string>
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_announcement_frequency">定期通知</string>
|
<string name="settings_announcement_frequency">定期通知</string>
|
||||||
<string name="settings_announcement_frequency_summary">音声通知間隔の最小時間</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">ポイント間の最短距離</string>
|
||||||
<string name="settings_min_recording_distance_summary">位置記録間の最短距離</string>
|
<string name="settings_min_recording_distance_summary">位置記録間の最短距離</string>
|
||||||
|
|||||||
@@ -147,15 +147,6 @@ limitations under the License.
|
|||||||
<item>1 min</item>
|
<item>1 min</item>
|
||||||
</string-array>
|
</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 -->
|
<!-- Other non-preference arrays -->
|
||||||
<array name="activity_types">
|
<array name="activity_types">
|
||||||
<item>nieznany</item>
|
<item>nieznany</item>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ limitations under the License.
|
|||||||
<string name="choose_track_message">Wybierz trasę do podglądu</string>
|
<string name="choose_track_message">Wybierz trasę do podglądu</string>
|
||||||
<string name="current_track">Obecna trasa</string>
|
<string name="current_track">Obecna trasa</string>
|
||||||
<string name="current_segment">Ostatni segment</string>
|
<string name="current_segment">Ostatni segment</string>
|
||||||
<string name="aggregated_stats">Statystyki Grupowe</string>
|
|
||||||
|
|
||||||
<!-- Error messages -->
|
<!-- Error messages -->
|
||||||
<string name="error_no_location_manager">Brak menedżera lokalizacji na urządzeniu!</string>
|
<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 -->
|
<!-- Used by DeleteAllDataHandler -->
|
||||||
<string name="are_you_sure_question">Jesteś pewien?</string>
|
<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="all_data_will_be_permanently_deleted">Wszystkie trasy i znaczniki zostaną usunięte z tego telefonu.</string>
|
||||||
<string name="track_will_be_permanently_deleted">Trasa i jej znaczniki zostanie całkowicie usunięta z 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 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 -->
|
<!-- Text and links when uploading to Google My Maps -->
|
||||||
<string name="new_map_description">Utworzono za pomocą My Tracks na Android.</string>
|
<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">Częstotliwość komunikatów</string>
|
||||||
<string name="settings_announcement_frequency_summary">Minimalny czas między dwoma komunikatami głosowymi</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">Min. odległość między punktami</string>
|
||||||
<string name="settings_min_recording_distance_summary">Najmniejsza odległość między dwoma zapisami twojej pozycji</string>
|
<string name="settings_min_recording_distance_summary">Najmniejsza odległość między dwoma zapisami twojej pozycji</string>
|
||||||
@@ -193,46 +192,15 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_share_url_only">Dziel się tylko odnośnikiem</string>
|
<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_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">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_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">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_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="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 -->
|
<!-- MyTracksSky -->
|
||||||
<string name="mytracks_sky">Satelity GPS</string>
|
<string name="mytracks_sky">Satelity GPS</string>
|
||||||
|
|
||||||
@@ -383,10 +351,8 @@ limitations under the License.
|
|||||||
<!-- SD card -->
|
<!-- 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_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_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_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_write_finished">Zakończono zapisywanie trasy na karcie SD.</string>
|
||||||
<string name="io_read_finished">Zakończono odczytywanie z karty SD.</string>
|
|
||||||
|
|
||||||
<!-- VersionChecker -->
|
<!-- VersionChecker -->
|
||||||
<string name="version_check_title">Sprawdzanie aktualizacji</string>
|
<string name="version_check_title">Sprawdzanie aktualizacji</string>
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ limitations under the License.
|
|||||||
<string name="settings_sharing">Compartilhamento</string>
|
<string name="settings_sharing">Compartilhamento</string>
|
||||||
<string name="settings_announcement_frequency">Tempo entre anúncios</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_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">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_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>
|
<string name="settings_max_recording_distance">Dist. máx. entre pontos</string>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ limitations under the License.
|
|||||||
<!-- Error messages -->
|
<!-- Error messages -->
|
||||||
<string name="error_no_location_manager">Ingen platshanterare hittades på denna enhet!</string>
|
<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_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_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 Dokument. 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_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_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>
|
<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_generic">Ett fel uppstod. Försök igen.</string>
|
||||||
<string name="error_track_does_not_exist">Spåret finns inte.</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_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 -->
|
<!-- 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_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_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">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_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_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_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_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_waypoint_inserted">En positionsmarkör har lagts in.</string>
|
||||||
<string name="status_statistics_inserted">En statistikmarkö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">Skickar ditt spår till Google.</string>
|
||||||
<string name="progress_message_reading_track">Läser spår...</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_preparing_track">Förbereder spår...</string>
|
||||||
<string name="progress_message_authenticating_mymaps">Autentiserar till Google Kartor...</string>
|
<string name="progress_message_authenticating_mymaps">Autentiserar till Google Maps...</string>
|
||||||
<string name="progress_message_authenticating_docs">Autentiserar till Google Dokument...</string>
|
<string name="progress_message_authenticating_docs">Autentiserar till Google Docs...</string>
|
||||||
<string name="progress_message_sending_docs">Skickar spår till Google Dokument...</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 Kartor...</string>
|
<string name="progress_message_sending_mymaps">Skickar spår till Google Maps...</string>
|
||||||
<string name="progress_message_creating_map">Skapar en ny Min Karta...</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="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>
|
<string name="progress_message_updating_track">Uppdaterar spårstatistik...</string>
|
||||||
|
|
||||||
<!-- Used by DeleteAllDataHandler -->
|
<!-- Used by DeleteAllDataHandler -->
|
||||||
@@ -110,9 +110,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<!-- TrackList -->
|
<!-- TrackList -->
|
||||||
<string name="tracklist_show_track">Visa</string>
|
<string name="tracklist_show_track">Visa</string>
|
||||||
<string name="tracklist_send_to_mymaps">Skicka till Google Mina Kartor</string>
|
<string name="tracklist_send_to_mymaps">Skicka till Google My Maps</string>
|
||||||
<string name="tracklist_send_to_new_mymap">Skicka till ny Google Mina Kartor</string>
|
<string name="tracklist_send_to_new_mymap">Skicka till New Google My Map</string>
|
||||||
<string name="tracklist_send_to_docs">Skicka till Google Dokument</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_send_to_google">Skicka till Google...</string>
|
||||||
<string name="tracklist_delete_track">Ta bort från telefonen...</string>
|
<string name="tracklist_delete_track">Ta bort från telefonen...</string>
|
||||||
<string name="tracklist_edit_track">Ändra detaljer</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_gpx">Spara en GPX-fil</string>
|
||||||
<string name="tracklist_save_as_kml">Spara en KML-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_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>
|
<string name="tracklist_btn_delete_all">Ta bort alla spår</string>
|
||||||
<!-- Export All -->
|
<!-- Export All -->
|
||||||
<string name="tracklist_btn_export_all">Exportera alla spår</string>
|
<string name="tracklist_btn_export_all">Exportera alla spår</string>
|
||||||
<string name="export_done">Exporteringen slutfördes</string>
|
<string name="export_done">Exporteringen slutfördes</string>
|
||||||
<string name="tracklist_pick_one">Välj ett spår. Tryck & håll för fler alternativ.</string>
|
<string name="tracklist_pick_one">Välj ett spår. TryckPress&håll för fler alternativ.</string>
|
||||||
<string name="tracklist_all_tracks">Alla spår:</string>
|
<string name="tracklist_all_tracks">Alla spår:</string>
|
||||||
<string name="tracklist_this_track">Det här spåret</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_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_gpx_file">En GPX-fil</string>
|
||||||
<string name="tracklist_share_kml_file">En KML-fil</string>
|
<string name="tracklist_share_kml_file">En KML-fil</string>
|
||||||
<string name="tracklist_share_csv_file">En CSV-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_show_waypoint">Visa på karta</string>
|
||||||
<string name="waypointslist_btn_insert_waypoint">Infoga positionspunkt</string>
|
<string name="waypointslist_btn_insert_waypoint">Infoga positionspunkt</string>
|
||||||
<string name="waypointslist_btn_insert_statistics">Infoga statistik</string>
|
<string name="waypointslist_btn_insert_statistics">Infoga statistik</string>
|
||||||
<string name="waypointslist_pick_one">Välj en markör. Tryck & håll för fler alternativ.</string>
|
<string name="waypointslist_pick_one">Välj en markör. TryckPress&håll för fler alternativ.</string>
|
||||||
|
|
||||||
<!-- MyTracks Details -->
|
<!-- MyTracks Details -->
|
||||||
<string name="trackdetails_title">Spårdetaljer</string>
|
<string name="trackdetails_title">Spårdetaljer</string>
|
||||||
<string name="trackdetails_name">Spårnamn</string>
|
<string name="trackdetails_name">Spårnamn</string>
|
||||||
<string name="trackdetails_description">Spårbeskrivning</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 -->
|
<!-- MyTracks Waypoint Details -->
|
||||||
<string name="waypointdetails_title">Markördetaljer</string>
|
<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">Meddelandefrekvens</string>
|
||||||
<string name="settings_announcement_frequency_summary">Minimumtid mellan två röstmeddelanden</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">Min distans mellan två punkter</string>
|
||||||
<string name="settings_min_recording_distance_summary">Minsta distans mellan två inspelningar av din position</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">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_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">Visa hastighet</string>
|
||||||
<string name="settings_report_speed_or_pace_summary_on">Hastighet kommer att visas</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_signal_sampling_frequency_summary">Tid mellan två inspelningar av telefonens signalstyrka</string>
|
||||||
|
|
||||||
<string name="settings_split_frequency">Delningsfrekvens</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>
|
<string name="pace_label">Tempo</string>
|
||||||
|
|
||||||
<!-- MyTracksSky -->
|
<!-- MyTracksSky -->
|
||||||
<string name="mytracks_sky">GPS-satelliter</string>
|
<string name="mytracks_sky">GPS-satteliter</string>
|
||||||
|
|
||||||
<!-- My Maps -->
|
<!-- 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="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_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="open_map">Välj den här kartan</string>
|
||||||
<string name="edit_map_details">Ändra kartdetaljer</string>
|
<string name="edit_map_details">Ändra kartdetaljer</string>
|
||||||
<string name="share_map">Dela kartan med vänner</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_title">Karttitel:</string>
|
||||||
<string name="map_description">Kartbeskrivning:</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_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_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="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>
|
<string name="close">Stäng</string>
|
||||||
@@ -240,7 +240,7 @@ limitations under the License.
|
|||||||
<!-- Visibility -->
|
<!-- Visibility -->
|
||||||
<string name="public_map">Publik</string>
|
<string name="public_map">Publik</string>
|
||||||
<string name="unlisted_map">Olistat</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 -->
|
<!-- Statistics View -->
|
||||||
<!-- Average Moving -->
|
<!-- Average Moving -->
|
||||||
@@ -253,14 +253,14 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="elevation_chart_name">Höjddiagram</string>
|
<string name="elevation_chart_name">Höjddiagram</string>
|
||||||
<string name="elevation_gain_label">Höjdvinst</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="latitude_label">Latitud</string>
|
||||||
<string name="longitude_label">Longitud</string>
|
<string name="longitude_label">Longitud</string>
|
||||||
|
|
||||||
<!-- Min -->
|
<!-- Min -->
|
||||||
<string name="min_elevation_label">Minimihöjd</string>
|
<string name="min_elevation_label">Min höjd</string>
|
||||||
<string name="min_grade_label">Minimilutning</string>
|
<string name="min_grade_label">Min lutning</string>
|
||||||
<string name="min_pace_label">Minimitempo</string>
|
<string name="min_pace_label">Min tempo</string>
|
||||||
|
|
||||||
<!-- Max -->
|
<!-- Max -->
|
||||||
<string name="max_elevation_label">Maxhöjd</string>
|
<string name="max_elevation_label">Maxhöjd</string>
|
||||||
@@ -272,12 +272,12 @@ limitations under the License.
|
|||||||
<!-- Chart Settings -->
|
<!-- Chart Settings -->
|
||||||
<string name="chart_settings">Diagraminställningar</string>
|
<string name="chart_settings">Diagraminställningar</string>
|
||||||
<!-- X Axis -->
|
<!-- 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_distance">Efter distans</string>
|
||||||
<string name="by_time">Efter tid</string>
|
<string name="by_time">Efter tid</string>
|
||||||
|
|
||||||
<!-- Y Axis -->
|
<!-- Y Axis -->
|
||||||
<string name="y_axis">Y-axel</string>
|
<string name="y_axis">Y Axel</string>
|
||||||
|
|
||||||
<!-- Units -->
|
<!-- Units -->
|
||||||
<!-- Time -->
|
<!-- Time -->
|
||||||
@@ -300,16 +300,16 @@ limitations under the License.
|
|||||||
|
|
||||||
<!-- Speed and Pace -->
|
<!-- Speed and Pace -->
|
||||||
<string name="kilometer_per_hour">km/h</string>
|
<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="min_per_kilometer">min/km</string>
|
||||||
<string name="per_kilometer">per kilometer</string>
|
<string name="per_kilometer">per kilometer</string>
|
||||||
<string name="mile_per_hour">mi/h</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="min_per_mile">min/mi</string>
|
||||||
<string name="per_mile">per mile</string>
|
<string name="per_mile">per mile</string>
|
||||||
|
|
||||||
<!-- Stats Labels -->
|
<!-- 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="total_time_label">Total tid</string>
|
||||||
<string name="time_label">Tid</string>
|
<string name="time_label">Tid</string>
|
||||||
<string name="sd_unavail">SD otillgängligt</string>
|
<string name="sd_unavail">SD otillgängligt</string>
|
||||||
@@ -336,8 +336,8 @@ limitations under the License.
|
|||||||
|
|
||||||
<!-- Send to Google -->
|
<!-- Send to Google -->
|
||||||
<string name="sendtogoogle_title">Skicka till...</string>
|
<string name="sendtogoogle_title">Skicka till...</string>
|
||||||
<string name="sendtogoogle_google_mymaps">Google Mina Kartor</string>
|
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
|
||||||
<string name="sendtogoogle_google_docs">Google Dokument</string>
|
<string name="sendtogoogle_google_docs">Google Docs</string>
|
||||||
<string name="sendtogoogle_send_now">Skicka nu</string>
|
<string name="sendtogoogle_send_now">Skicka nu</string>
|
||||||
<string name="sendtogoogle_create_new_map">Skapa en ny karta</string>
|
<string name="sendtogoogle_create_new_map">Skapa en ny karta</string>
|
||||||
<string name="sendtogoogle_pick_existing_map">Välj en befintlig 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="error_rate"> Felfrekvens: %1$d</string>
|
||||||
<string name="signal_to_noise_ratio"> Signal / brus-förhållande: %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="ecio"> Ec/lo: %1$.1f dB</string>
|
||||||
<string name="gsm_strength">GSM-styrka: %1$d</string>
|
<string name="gsm_strength">GSM styrka: %1$d</string>
|
||||||
<string name="cdma_strength">CDMA-styrka: %1$d dBm</string>
|
<string name="cdma_strength">CDMA styrka: %1$d dBm</string>
|
||||||
<string name="evdo_strength">EVDO-styrka: %1$d dBm</string>
|
<string name="evdo_strength">EVDO styrka: %1$d dBm</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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 上的 <a href=\'http://mytracks.appspot.com\'>My Tracks</a> 生成.</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>
|
|
||||||
@@ -236,24 +236,6 @@ limitations under the License.
|
|||||||
<item>1</item>
|
<item>1</item>
|
||||||
</string-array>
|
</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 -->
|
<!-- Other non-preference arrays -->
|
||||||
<array name="activity_types">
|
<array name="activity_types">
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<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="announcement_frequency_key">announcementFrequency</string>
|
||||||
<string name="auto_resume_track_timeout_key">autoResumeTrackTimeout</string>
|
|
||||||
<string name="bluetooth_device_key">bluetoothDevice</string>
|
<string name="bluetooth_device_key">bluetoothDevice</string>
|
||||||
<string name="bluetooth_pairing_key">bluetoothPairing</string>
|
<string name="bluetooth_pairing_key">bluetoothPairing</string>
|
||||||
<string name="bluetooth_sensor_key">bluetoothSensor</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="min_required_accuracy_key">minRequiredAccuracy</string>
|
||||||
<string name="modular_stats_display_key">modularStatsDisplay</string>
|
<string name="modular_stats_display_key">modularStatsDisplay</string>
|
||||||
<string name="pick_existing_map_key">pickExistingMap</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="report_speed_key">reportSpeed</string>
|
||||||
|
<string name="selected_track_key">selectedTrack</string>
|
||||||
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
|
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
|
||||||
<string name="send_to_docs_key">sendToDocs</string>
|
<string name="send_to_docs_key">sendToDocs</string>
|
||||||
<string name="send_to_my_maps_key">sendToMyMaps</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="share_url_only_key">shareUrlOnly</string>
|
||||||
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
||||||
<string name="split_frequency_key">splitFrequency</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>
|
||||||
@@ -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>
|
|
||||||
@@ -32,7 +32,6 @@ limitations under the License.
|
|||||||
<string name="choose_track_message">Please select the track to view</string>
|
<string name="choose_track_message">Please select the track to view</string>
|
||||||
<string name="current_track">Current track</string>
|
<string name="current_track">Current track</string>
|
||||||
<string name="current_segment">Last segment</string>
|
<string name="current_segment">Last segment</string>
|
||||||
<string name="aggregated_stats">Aggregated Statistics</string>
|
|
||||||
|
|
||||||
<!-- Error messages -->
|
<!-- Error messages -->
|
||||||
<string name="error_no_location_manager">No location manager was found on this device!</string>
|
<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_recording">Recording</string>
|
||||||
<string name="settings_sharing">Sharing</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">Announcement frequency</string>
|
||||||
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</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">Min distance between points</string>
|
||||||
<string name="settings_min_recording_distance_summary">Smallest distance between two recordings of your position</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_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_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_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_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="pace_label">Pace</string>
|
||||||
|
|
||||||
<string name="eclair_required">Only supported in Android 2.0 or above</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">Sensor Type</string>
|
||||||
<string name="settings_sensor_type_summary">The type of sensor to connect to.</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">Sensor Id</string>
|
||||||
<string name="settings_sensor_id_summary">The Id sent to sensors.</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="sensor_id_malformed">The Id must be an integer value.</string>
|
||||||
|
|
||||||
<string name="bluetooth_sensor_title">Bluetooth sensor</string>
|
<string name="bluetooth_sensor_title">Bluetooth sensor</string>
|
||||||
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to.</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_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_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_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 -->
|
<!-- MyTracksSky -->
|
||||||
<string name="mytracks_sky">GPS Satellites</string>
|
<string name="mytracks_sky">GPS Satellites</string>
|
||||||
|
|
||||||
@@ -413,10 +391,8 @@ limitations under the License.
|
|||||||
<!-- SD card -->
|
<!-- 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_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_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_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_write_finished">Finished writing the track to SD card.</string>
|
||||||
<string name="io_read_finished">Finished reading from SD card.</string>
|
|
||||||
|
|
||||||
<!-- VersionChecker -->
|
<!-- VersionChecker -->
|
||||||
<string name="version_check_title">Upgrade Check</string>
|
<string name="version_check_title">Upgrade Check</string>
|
||||||
|
|||||||
@@ -91,22 +91,6 @@ limitations under the License.
|
|||||||
android:entries="@array/task_frequency_options"
|
android:entries="@array/task_frequency_options"
|
||||||
android:entryValues="@array/task_frequency_values"
|
android:entryValues="@array/task_frequency_values"
|
||||||
android:defaultValue="-1" />
|
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>
|
</PreferenceScreen>
|
||||||
|
|
||||||
<!-- Sharing settings -->
|
<!-- Sharing settings -->
|
||||||
@@ -125,20 +109,4 @@ limitations under the License.
|
|||||||
android:summaryOff="@string/settings_share_url_only_summary_off"
|
android:summaryOff="@string/settings_share_url_only_summary_off"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
</PreferenceScreen>
|
</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>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -25,8 +25,5 @@ public class MyMapsConstants {
|
|||||||
"http://maps.google.com/maps/ms";
|
"http://maps.google.com/maps/ms";
|
||||||
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
|
public static final String MAPSHOP_SERVICE = MapsClient.SERVICE;
|
||||||
|
|
||||||
/**
|
|
||||||
* Private constructor to prevent instantiation.
|
|
||||||
*/
|
|
||||||
private MyMapsConstants() { }
|
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.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
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.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
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.MyTracksUtils;
|
||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -177,15 +175,11 @@ public class ChartActivity extends Activity implements
|
|||||||
final int idColumnIdx =
|
final int idColumnIdx =
|
||||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||||
ArrayList<double[]> data = new ArrayList<double[]>();
|
ArrayList<double[]> data = new ArrayList<double[]>();
|
||||||
// Need two locations so we can keep track of the last location.
|
|
||||||
Location location = new Location("");
|
|
||||||
do {
|
do {
|
||||||
lastSeenLocationId = cursor.getLong(idColumnIdx);
|
lastSeenLocationId = cursor.getLong(idColumnIdx);
|
||||||
providerUtils.fillLocation(cursor, location);
|
Location location = providerUtils.createLocation(cursor);
|
||||||
if (MyTracksUtils.isValidLocation(location)) {
|
if (location != null && MyTracksUtils.isValidLocation(location)) {
|
||||||
double[] point = new double[3];
|
data.add(getDataPoint(location, track));
|
||||||
location = getDataPoint(location, track, point);
|
|
||||||
data.add(point);
|
|
||||||
}
|
}
|
||||||
} while (cursor.moveToPrevious());
|
} while (cursor.moveToPrevious());
|
||||||
cv.addDataPoints(data);
|
cv.addDataPoints(data);
|
||||||
@@ -243,11 +237,6 @@ public class ChartActivity extends Activity implements
|
|||||||
MyTracks.getInstance().setChartActivity(this);
|
MyTracks.getInstance().setChartActivity(this);
|
||||||
providerUtils = MyTracksProviderUtils.Factory.get(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);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setContentView(R.layout.mytracks_elevation);
|
setContentView(R.layout.mytracks_elevation);
|
||||||
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
|
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
|
||||||
@@ -298,13 +287,13 @@ public class ChartActivity extends Activity implements
|
|||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
|
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
|
||||||
// Check for any new locations and append them to the currently
|
// Check for any new locations and append them to the currently
|
||||||
// recording track.
|
// recording track:
|
||||||
if (recordingTrackId < 0) {
|
if (!MyTracks.getInstance().isRecording()) {
|
||||||
// No track is being recorded. We should not be here.
|
// No track is being recorded. We should not be here.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedTrackId != recordingTrackId) {
|
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.
|
// bother.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -455,8 +444,7 @@ public class ChartActivity extends Activity implements
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case MyTracksConstants.MENU_CHART_SETTINGS:
|
case MyTracksConstants.MENU_CHART_SETTINGS:
|
||||||
MyTracks.getInstance().getDialogManager().showDialogSafely(
|
MyTracks.getInstance().showDialogSafely(MyTracks.DIALOG_CHART_SETTINGS);
|
||||||
DialogManager.DIALOG_CHART_SETTINGS);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
@@ -469,14 +457,11 @@ public class ChartActivity extends Activity implements
|
|||||||
* data[1] = the elevation
|
* data[1] = the elevation
|
||||||
* data[2] = the speed
|
* data[2] = the speed
|
||||||
*
|
*
|
||||||
* This must be called in order for each point.
|
* @param location a location
|
||||||
*
|
* @return the data 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
|
|
||||||
*/
|
*/
|
||||||
private Location getDataPoint(Location location, Track track, double[] result) {
|
public double[] getDataPoint(Location location, Track track) {
|
||||||
|
double[] result = new double[3];
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case BY_DISTANCE:
|
case BY_DISTANCE:
|
||||||
result[0] = profileLength;
|
result[0] = profileLength;
|
||||||
@@ -523,16 +508,8 @@ public class ChartActivity extends Activity implements
|
|||||||
// Format as hours per unit
|
// Format as hours per unit
|
||||||
result[2] = (60.0 / result[2]);
|
result[2] = (60.0 / result[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Location oldLastLocation = lastLocation;
|
|
||||||
lastLocation = location;
|
lastLocation = location;
|
||||||
|
return result;
|
||||||
if (oldLastLocation == null) {
|
|
||||||
// No previous location, but return a blank one for reuse
|
|
||||||
return new Location("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldLastLocation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -575,8 +552,6 @@ public class ChartActivity extends Activity implements
|
|||||||
try {
|
try {
|
||||||
final ArrayList<double[]> theData = new ArrayList<double[]>();
|
final ArrayList<double[]> theData = new ArrayList<double[]>();
|
||||||
int points = 0;
|
int points = 0;
|
||||||
// Need two locations so we can keep track of the last location.
|
|
||||||
Location location = new Location("");
|
|
||||||
while (lastLocationRead < track.getStopId()) {
|
while (lastLocationRead < track.getStopId()) {
|
||||||
cursor = providerUtils.getLocationsCursor(
|
cursor = providerUtils.getLocationsCursor(
|
||||||
selectedTrackId, lastLocationRead, bufferSize, false);
|
selectedTrackId, lastLocationRead, bufferSize, false);
|
||||||
@@ -588,12 +563,11 @@ public class ChartActivity extends Activity implements
|
|||||||
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
points++;
|
points++;
|
||||||
providerUtils.fillLocation(cursor, location);
|
Location location = providerUtils.createLocation(cursor);
|
||||||
if (MyTracksUtils.isValidLocation(location)) {
|
if (MyTracksUtils.isValidLocation(location)) {
|
||||||
lastLocationRead = lastSeenLocationId =
|
lastLocationRead = lastSeenLocationId =
|
||||||
cursor.getLong(idColumnIdx);
|
cursor.getLong(idColumnIdx);
|
||||||
double[] point = new double[3];
|
double[] point = getDataPoint(location, track);
|
||||||
location = getDataPoint(location, track, point);
|
|
||||||
if (points % chartSamplingFrequency == 0) {
|
if (points % chartSamplingFrequency == 0) {
|
||||||
theData.add(point);
|
theData.add(point);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,10 +66,6 @@ public class ChartSettingsDialog extends Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setup(ChartActivity chart) {
|
public void setup(ChartActivity chart) {
|
||||||
if (chart == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
|
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
|
||||||
rd.check(chart.getMode() == Mode.BY_DISTANCE
|
rd.check(chart.getMode() == Mode.BY_DISTANCE
|
||||||
? R.id.chart_settings_by_distance
|
? R.id.chart_settings_by_distance
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.graphics.Path;
|
|||||||
import java.text.DecimalFormat;
|
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
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -511,8 +511,8 @@ public class ChartView extends View {
|
|||||||
// Dimensions have changed (for example due to orientation change):
|
// Dimensions have changed (for example due to orientation change):
|
||||||
w = c.getWidth();
|
w = c.getWidth();
|
||||||
h = c.getHeight();
|
h = c.getHeight();
|
||||||
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
effectiveWidth = w - leftBorder - RIGHT_BORDER;
|
||||||
effectiveHeight = Math.max(0, h - TOP_BORDER - BOTTOM_BORDER);
|
effectiveHeight = h - TOP_BORDER - BOTTOM_BORDER;
|
||||||
setupPath();
|
setupPath();
|
||||||
}
|
}
|
||||||
c.save();
|
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.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
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;
|
||||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -47,7 +45,7 @@ public class ExportAllTracks {
|
|||||||
public static final int KML_OPTION_INDEX = 1;
|
public static final int KML_OPTION_INDEX = 1;
|
||||||
public static final int CSV_OPTION_INDEX = 2;
|
public static final int CSV_OPTION_INDEX = 2;
|
||||||
|
|
||||||
private final Activity activity;
|
private final Context context;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private ProgressDialog progress;
|
private ProgressDialog progress;
|
||||||
|
|
||||||
@@ -71,11 +69,11 @@ public class ExportAllTracks {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public ExportAllTracks(Activity activity) {
|
public ExportAllTracks(Context c) {
|
||||||
this.activity = activity;
|
this.context = c;
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Starting");
|
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.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
|
||||||
builder.setPositiveButton(R.string.ok, positiveClick);
|
builder.setPositiveButton(R.string.ok, positiveClick);
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
@@ -108,11 +106,11 @@ public class ExportAllTracks {
|
|||||||
*/
|
*/
|
||||||
private void aquireLocksAndExport() {
|
private void aquireLocksAndExport() {
|
||||||
SharedPreferences prefs =
|
SharedPreferences prefs =
|
||||||
activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
long recordingTrackId = -1;
|
long recordingTrackId = -1;
|
||||||
if (prefs != null) {
|
if (prefs != null) {
|
||||||
recordingTrackId =
|
recordingTrackId =
|
||||||
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
|
prefs.getLong(context.getString(R.string.recording_track_key), -1);
|
||||||
}
|
}
|
||||||
if (recordingTrackId != -1) {
|
if (recordingTrackId != -1) {
|
||||||
acquireWakeLock();
|
acquireWakeLock();
|
||||||
@@ -128,12 +126,12 @@ public class ExportAllTracks {
|
|||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
|
||||||
}
|
}
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Done");
|
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) {
|
private void makeProgressDialog(final int trackCount) {
|
||||||
String exportMsg = activity.getString(R.string.tracklist_btn_export_all);
|
String exportMsg = context.getString(R.string.tracklist_btn_export_all);
|
||||||
progress = new ProgressDialog(activity);
|
progress = new ProgressDialog(context);
|
||||||
progress.setIcon(android.R.drawable.ic_dialog_info);
|
progress.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
progress.setTitle(exportMsg);
|
progress.setTitle(exportMsg);
|
||||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
@@ -151,25 +149,22 @@ public class ExportAllTracks {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
MyTracksProviderUtils providerUtils =
|
MyTracksProviderUtils providerUtils =
|
||||||
MyTracksProviderUtils.Factory.get(activity);
|
MyTracksProviderUtils.Factory.get(context);
|
||||||
cursor = providerUtils.getTracksCursor("");
|
cursor = providerUtils.getTracksCursor("");
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int trackCount = cursor.getCount();
|
final int trackCount = cursor.getCount();
|
||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
|
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
|
||||||
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||||
activity.runOnUiThread(new Runnable() {
|
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
makeProgressDialog(trackCount);
|
makeProgressDialog(trackCount);
|
||||||
}
|
}});
|
||||||
});
|
|
||||||
|
|
||||||
for (int i = 0; cursor.moveToNext(); i++) {
|
for (int i = 0; cursor.moveToNext(); i++) {
|
||||||
final int status = i;
|
final int status = i;
|
||||||
activity.runOnUiThread(new Runnable() {
|
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (progress == null) {
|
if (progress == null) {
|
||||||
@@ -182,27 +177,16 @@ public class ExportAllTracks {
|
|||||||
|
|
||||||
long id = cursor.getLong(idxTrackId);
|
long id = cursor.getLong(idxTrackId);
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
|
||||||
TrackWriter writer =
|
TrackWriterFactory.newWriter(context, providerUtils, id, format)
|
||||||
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
.writeTrack();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
if (progress != null) {
|
}
|
||||||
synchronized (this) {
|
if (progress != null) {
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
progress = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +198,7 @@ public class ExportAllTracks {
|
|||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
|
||||||
try {
|
try {
|
||||||
PowerManager pm =
|
PowerManager pm =
|
||||||
(PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
if (pm == null) {
|
if (pm == null) {
|
||||||
Log.e(MyTracksConstants.TAG,
|
Log.e(MyTracksConstants.TAG,
|
||||||
"ExportAllTracks: Power manager not found!");
|
"ExportAllTracks: Power manager not found!");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import android.view.MenuItem;
|
|||||||
*/
|
*/
|
||||||
class MenuManager {
|
class MenuManager {
|
||||||
|
|
||||||
private final MyTracks activity;
|
private MyTracks activity;
|
||||||
|
|
||||||
public MenuManager(MyTracks activity) {
|
public MenuManager(MyTracks activity) {
|
||||||
this.activity = 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_tracks).setEnabled(hasRecorded);
|
||||||
menu.findItem(R.id.menu_list_markers)
|
menu.findItem(R.id.menu_list_markers)
|
||||||
.setEnabled(hasRecorded && hasSelectedTrack);
|
.setEnabled(hasRecorded && hasSelectedTrack);
|
||||||
menu.findItem(R.id.menu_start_recording)
|
MenuItem startRecording = menu.findItem(R.id.menu_start_recording);
|
||||||
.setEnabled(!isRecording)
|
startRecording.setEnabled(!isRecording);
|
||||||
.setVisible(!isRecording);
|
startRecording.setVisible(!isRecording);
|
||||||
menu.findItem(R.id.menu_stop_recording)
|
MenuItem stopRecording = menu.findItem(R.id.menu_stop_recording);
|
||||||
.setEnabled(isRecording)
|
stopRecording.setEnabled(isRecording);
|
||||||
.setVisible(isRecording);
|
stopRecording.setVisible(isRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
@@ -69,7 +69,7 @@ class MenuManager {
|
|||||||
}
|
}
|
||||||
case R.id.menu_list_markers: {
|
case R.id.menu_list_markers: {
|
||||||
Intent startIntent = new Intent(activity, MyTracksWaypointsList.class);
|
Intent startIntent = new Intent(activity, MyTracksWaypointsList.class);
|
||||||
startIntent.putExtra("trackid", activity.getSelectedTrackId());
|
startIntent.putExtra("trackid", activity.getSelectedTrack());
|
||||||
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
|
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -78,18 +78,13 @@ class MenuManager {
|
|||||||
activity.startActivity(startIntent);
|
activity.startActivity(startIntent);
|
||||||
return true;
|
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: {
|
case R.id.menu_help: {
|
||||||
Intent startIntent = new Intent(activity, WelcomeActivity.class);
|
Intent startIntent = new Intent(activity, WelcomeActivity.class);
|
||||||
activity.startActivity(startIntent);
|
activity.startActivity(startIntent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MyTracksConstants.MENU_CLEAR_MAP: {
|
case MyTracksConstants.MENU_CLEAR_MAP: {
|
||||||
activity.setSelectedTrackId(-1);
|
activity.setSelectedTrack(-1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,6 @@ public abstract class MyTracksConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String GPS_PROVIDER = "gps";
|
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:
|
* onActivityResult request codes:
|
||||||
*/
|
*/
|
||||||
@@ -173,13 +167,6 @@ public abstract class MyTracksConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String ACCOUNT_TYPE = "com.google";
|
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) {
|
public static int getActionFromMenuId(int menuId) {
|
||||||
switch (menuId) {
|
switch (menuId) {
|
||||||
case MyTracksConstants.MENU_SEND_TO_GOOGLE:
|
case MyTracksConstants.MENU_SEND_TO_GOOGLE:
|
||||||
|
|||||||
@@ -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.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
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.stats.TripStatistics;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapActivity;
|
import com.google.android.maps.MapActivity;
|
||||||
@@ -51,14 +49,14 @@ import android.os.HandlerThread;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.View.OnCreateContextMenuListener;
|
import android.view.View.OnCreateContextMenuListener;
|
||||||
|
import android.view.Window;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
@@ -71,9 +69,8 @@ import android.widget.Toast;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class MyTracksMap extends MapActivity
|
public class MyTracksMap extends MapActivity
|
||||||
implements View.OnTouchListener, View.OnClickListener,
|
implements LocationListener, SensorEventListener, View.OnTouchListener,
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final int TRACKPOINT_BUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
// Saved instance state keys:
|
// Saved instance state keys:
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -84,9 +81,9 @@ public class MyTracksMap extends MapActivity
|
|||||||
public static final String KEY_HAVE_GOOD_FIX = "haveGoodFix";
|
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.
|
* The id of the currently recording track.
|
||||||
@@ -99,12 +96,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
*/
|
*/
|
||||||
private boolean keepMyLocationVisible;
|
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
|
* 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
|
* provider. This is used to determine which locations are new compared to the
|
||||||
@@ -118,7 +109,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
private double variation;
|
private double variation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From the shared preferences.
|
* From the shared preferences:
|
||||||
*/
|
*/
|
||||||
private int minRequiredAccuracy =
|
private int minRequiredAccuracy =
|
||||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
||||||
@@ -135,31 +126,32 @@ public class MyTracksMap extends MapActivity
|
|||||||
private Location currentLocation;
|
private Location currentLocation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread with a looper. Post to updateTrackHandler to execute
|
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
|
||||||
* {@link Runnable}s on this thread.
|
* this thread.
|
||||||
*/
|
*/
|
||||||
private HandlerThread updateTrackThread;
|
private final HandlerThread updateTrackThread =
|
||||||
|
new HandlerThread("updateTrackThread");
|
||||||
|
|
||||||
/**
|
/** Handler for updateTrackThread */
|
||||||
* Handler for updateTrackThread.
|
|
||||||
*/
|
|
||||||
private Handler updateTrackHandler;
|
private Handler updateTrackHandler;
|
||||||
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
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
|
* A runnable that updates the track from the provider (looking for points
|
||||||
* added after "lastSeenLocationId").
|
* added after "lastSeenLocationId".
|
||||||
*/
|
*/
|
||||||
private final Runnable updateTrackRunnable = new Runnable() {
|
private final Runnable updateTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!isATrackSelected()) {
|
if (selectedTrack == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readAllNewTrackPoints();
|
readAllNewTrackPoints();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -170,13 +162,11 @@ public class MyTracksMap extends MapActivity
|
|||||||
private Runnable restoreTrackRunnable = new Runnable() {
|
private Runnable restoreTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!isATrackSelected()) {
|
if (selectedTrack == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOverlay.clearPoints();
|
mapOverlay.clearPoints();
|
||||||
firstSeenLocationId = -1;
|
lastSeenLocationId = selectedTrack.getStartId();
|
||||||
lastSeenLocationId = -1;
|
|
||||||
readAllNewTrackPoints();
|
readAllNewTrackPoints();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -187,7 +177,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
private final Runnable restoreWaypointsRunnable = new Runnable() {
|
private final Runnable restoreWaypointsRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!isATrackSelected()) {
|
if (selectedTrack == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,12 +187,15 @@ public class MyTracksMap extends MapActivity
|
|||||||
// We will silently drop extra waypoints to make the app responsive.
|
// We will silently drop extra waypoints to make the app responsive.
|
||||||
// TODO: Try to only load the waypoints in the view port.
|
// TODO: Try to only load the waypoints in the view port.
|
||||||
cursor = providerUtils.getWaypointsCursor(
|
cursor = providerUtils.getWaypointsCursor(
|
||||||
selectedTrackId, 0,
|
selectedTrack.getId(), 0,
|
||||||
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null) {
|
||||||
do {
|
if (cursor.moveToFirst()) {
|
||||||
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
|
do {
|
||||||
} while (cursor.moveToNext());
|
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||||
|
mapOverlay.addWaypoint(wpt);
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", 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
|
* A runnable intended to be posted to the updateTrackThread after the
|
||||||
* the selected track changes. It will post to the UI thread to update
|
* selected track changes. It will post to the ui thread to update the screen
|
||||||
* the screen elements and move the map to show the selected track.
|
* elements and move the map to show the selected track.
|
||||||
*/
|
*/
|
||||||
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
uiHandler.post(new Runnable() {
|
uiHandler.post(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
showTrack(selectedTrackId);
|
showTrack(selectedTrack);
|
||||||
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
mapOverlay.setSelectedTrack(selectedTrack);
|
||||||
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
mapOverlay.setShowEndMarker(selectedTrack == null ||
|
||||||
|
selectedTrack.getId() != recordingTrackId);
|
||||||
mapView.invalidate();
|
mapView.invalidate();
|
||||||
busyPane.setVisibility(View.GONE);
|
busyPane.setVisibility(View.GONE);
|
||||||
updateOptionsButton();
|
updateOptionsButton();
|
||||||
@@ -285,11 +279,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onCreate");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onCreate");
|
||||||
super.onCreate(bundle);
|
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);
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
|
||||||
// We don't need a window title bar:
|
// We don't need a window title bar:
|
||||||
@@ -323,7 +312,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
locationManager =
|
locationManager =
|
||||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
|
||||||
updateTrackThread = new HandlerThread("updateTrackThread");
|
|
||||||
updateTrackThread.start();
|
updateTrackThread.start();
|
||||||
updateTrackHandler = new Handler(updateTrackThread.getLooper());
|
updateTrackHandler = new Handler(updateTrackThread.getLooper());
|
||||||
|
|
||||||
@@ -333,7 +321,14 @@ public class MyTracksMap extends MapActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
|
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,
|
// No track, or one other than the recording track is selected,
|
||||||
// don't bother.
|
// don't bother.
|
||||||
return;
|
return;
|
||||||
@@ -349,7 +344,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"MyTracksMap: ContentObserver.onChange waypoints");
|
"MyTracksMap: ContentObserver.onChange waypoints");
|
||||||
if (!isATrackSelected()) {
|
if (selectedTrack == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateTrackHandler.post(restoreWaypointsRunnable);
|
updateTrackHandler.post(restoreWaypointsRunnable);
|
||||||
@@ -357,49 +352,38 @@ public class MyTracksMap extends MapActivity
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read shared preferences and register change listener.
|
// Read shared preferences and register change listener:
|
||||||
sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
SharedPreferences preferences =
|
||||||
if (sharedPreferences != null) {
|
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
reloadSharedPreferences(sharedPreferences, null);
|
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();
|
updateOptionsButton();
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
protected void setupZoomControls() {
|
||||||
|
mapView.setBuiltInZoomControls(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
|
// Final cleanup before activity is destroyed.
|
||||||
|
// May not be called at all in some situations.
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
||||||
|
|
||||||
if (updateTrackThread != null) {
|
|
||||||
ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
|
|
||||||
updateTrackThread);
|
|
||||||
}
|
|
||||||
if (sharedPreferences != null) {
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDestroy();
|
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
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
// Called after onCreate or onStop.
|
// Called after onCreate or onStop.
|
||||||
@@ -442,9 +426,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
|
||||||
super.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
|
// Make sure any updates that might have happened are propagated to the
|
||||||
// Map overlay:
|
// Map overlay:
|
||||||
observer.onChange(false);
|
observer.onChange(false);
|
||||||
@@ -465,9 +446,10 @@ public class MyTracksMap extends MapActivity
|
|||||||
|
|
||||||
// While this activity was paused the user may have deleted the selected
|
// While this activity was paused the user may have deleted the selected
|
||||||
// track. In that case the map overlay needs to be cleared:
|
// 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.
|
// The recording track must have been deleted meanwhile.
|
||||||
mapOverlay.setTrackDrawingEnabled(false);
|
mapOverlay.setSelectedTrack(null);
|
||||||
mapView.invalidate();
|
mapView.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -529,10 +511,10 @@ public class MyTracksMap extends MapActivity
|
|||||||
+ gpsProvider.getName());
|
+ gpsProvider.getName());
|
||||||
}
|
}
|
||||||
locationManager.requestLocationUpdates(gpsProvider.getName(),
|
locationManager.requestLocationUpdates(gpsProvider.getName(),
|
||||||
0 /*minTime*/, 0 /*minDist*/, locationListener);
|
0 /*minTime*/, 0 /*minDist*/, this);
|
||||||
try {
|
try {
|
||||||
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
|
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
|
||||||
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, locationListener);
|
1000 * 60 * 5 /*minTime*/, 0 /*minDist*/, this);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// If anything at all goes wrong with getting a cell location do not
|
// If anything at all goes wrong with getting a cell location do not
|
||||||
// abort. Cell location is not essential to this app.
|
// abort. Cell location is not essential to this app.
|
||||||
@@ -550,7 +532,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"MyTracksMap: Now registering sensor listeners.");
|
"MyTracksMap: Now registering sensor listeners.");
|
||||||
sensorManager.registerListener(
|
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) {
|
if (locationManager != null) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"MyTracksMap: Now unregistering location listeners.");
|
"MyTracksMap: Now unregistering location listeners.");
|
||||||
locationManager.removeUpdates(locationListener);
|
locationManager.removeUpdates(this);
|
||||||
}
|
}
|
||||||
if (sensorManager != null) {
|
if (sensorManager != null) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"MyTracksMap: Now unregistering sensor listeners.");
|
"MyTracksMap: Now unregistering sensor listeners.");
|
||||||
sensorManager.unregisterListener(sensorListener);
|
sensorManager.unregisterListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,7 +576,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
*/
|
*/
|
||||||
private void updateOptionsButton() {
|
private void updateOptionsButton() {
|
||||||
optionsBtn.setVisibility(
|
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.
|
* 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) {
|
public void showTrack(Track track) {
|
||||||
if (mapView == null) {
|
if (track == null || mapView == null || track.getNumberOfPoints() < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
if (track == null || track.getNumberOfPoints() < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TripStatistics stats = track.getStatistics();
|
TripStatistics stats = track.getStatistics();
|
||||||
int bottom = stats.getBottom();
|
int latSpanE6 = stats.getTop() - stats.getBottom();
|
||||||
int left = stats.getLeft();
|
int lonSpanE6 = stats.getRight() - stats.getLeft();
|
||||||
int latSpanE6 = stats.getTop() - bottom;
|
if (latSpanE6 > 0 && latSpanE6 < 180E6 && lonSpanE6 > 0
|
||||||
int lonSpanE6 = stats.getRight() - left;
|
&& lonSpanE6 < 180E6) {
|
||||||
if (latSpanE6 > 0
|
|
||||||
&& latSpanE6 < 180E6
|
|
||||||
&& lonSpanE6 > 0
|
|
||||||
&& lonSpanE6 < 360E6) {
|
|
||||||
keepMyLocationVisible = false;
|
keepMyLocationVisible = false;
|
||||||
GeoPoint center = new GeoPoint(
|
GeoPoint center = new GeoPoint(
|
||||||
bottom + latSpanE6 / 2,
|
stats.getBottom() + latSpanE6 / 2,
|
||||||
left + lonSpanE6 / 2);
|
stats.getLeft() + lonSpanE6 / 2);
|
||||||
if (MyTracksUtils.isValidGeoPoint(center)) {
|
if (MyTracksUtils.isValidGeoPoint(center)) {
|
||||||
mapView.getController().setCenter(center);
|
mapView.getController().setCenter(center);
|
||||||
mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
|
mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
|
||||||
@@ -701,33 +673,24 @@ public class MyTracksMap extends MapActivity
|
|||||||
* @param trackId a given track id
|
* @param trackId a given track id
|
||||||
*/
|
*/
|
||||||
public void setSelectedTrack(final long trackId) {
|
public void setSelectedTrack(final long trackId) {
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.setSelectedTrack: "
|
if (selectedTrack != null && selectedTrack.getId() == trackId) {
|
||||||
+ "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId);
|
|
||||||
|
|
||||||
if (selectedTrackId == trackId) {
|
|
||||||
// Selected track did not change, nothing to do.
|
// Selected track did not change, nothing to do.
|
||||||
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
mapOverlay.setSelectedTrack(selectedTrack);
|
||||||
updateOptionsButton();
|
|
||||||
mapView.invalidate();
|
mapView.invalidate();
|
||||||
|
updateOptionsButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackId < 0) {
|
if (trackId < 0) {
|
||||||
// Remove selection.
|
// Remove selection.
|
||||||
selectedTrackId = -1;
|
selectedTrack = null;
|
||||||
mapOverlay.setTrackDrawingEnabled(false);
|
mapOverlay.setSelectedTrack(null);
|
||||||
mapOverlay.clearWaypoints();
|
mapOverlay.clearWaypoints();
|
||||||
updateOptionsButton();
|
updateOptionsButton();
|
||||||
mapView.invalidate();
|
mapView.invalidate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
busyPane.setVisibility(View.VISIBLE);
|
busyPane.setVisibility(View.VISIBLE);
|
||||||
selectedTrackId = trackId;
|
selectedTrack = providerUtils.getTrack(trackId);
|
||||||
loadSelectedTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadSelectedTrack() {
|
|
||||||
updateTrackHandler.post(restoreTrackRunnable);
|
updateTrackHandler.post(restoreTrackRunnable);
|
||||||
updateTrackHandler.post(restoreWaypointsRunnable);
|
updateTrackHandler.post(restoreWaypointsRunnable);
|
||||||
updateTrackHandler.post(setSelectedTrackRunnable);
|
updateTrackHandler.post(setSelectedTrackRunnable);
|
||||||
@@ -783,7 +746,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
menu.setHeaderTitle(R.string.tracklist_this_track);
|
menu.setHeaderTitle(R.string.tracklist_this_track);
|
||||||
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
|
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
|
||||||
R.string.tracklist_edit_track);
|
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,
|
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
|
||||||
R.string.tracklist_send_to_google);
|
R.string.tracklist_send_to_google);
|
||||||
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
||||||
@@ -820,7 +784,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||||
if (!super.onMenuItemSelected(featureId, item)) {
|
if (!super.onMenuItemSelected(featureId, item)) {
|
||||||
if (isATrackSelected()) {
|
if (selectedTrack != null) {
|
||||||
MyTracks.getInstance().onActivityResult(
|
MyTracks.getInstance().onActivityResult(
|
||||||
MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK,
|
MyTracksConstants.getActionFromMenuId(item.getItemId()), RESULT_OK,
|
||||||
new Intent());
|
new Intent());
|
||||||
@@ -881,7 +845,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (v == messagePane) {
|
if (v == messagePane) {
|
||||||
launchMyLocationSettings();
|
launchMyLocationSettings();
|
||||||
} else if (v == optionsBtn) {
|
}
|
||||||
|
if (v == optionsBtn) {
|
||||||
optionsBtn.performLongClick();
|
optionsBtn.performLongClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,190 +866,156 @@ public class MyTracksMap extends MapActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||||
final SharedPreferences sharedPreferences, final String key) {
|
String key) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"MyTracksMap.onSharedPreferenceChanged: " + key);
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
uiHandler.post(new Runnable() {
|
if (key.equals(getString(R.string.min_required_accuracy_key))) {
|
||||||
@Override
|
minRequiredAccuracy = sharedPreferences.getInt(
|
||||||
public void run() {
|
getString(R.string.min_required_accuracy_key),
|
||||||
reloadSharedPreferences(sharedPreferences, 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
|
||||||
@Override
|
public void onProviderEnabled(String provider) {
|
||||||
public void onProviderEnabled(String provider) {
|
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
messageText.setText(R.string.wait_for_fix);
|
||||||
messageText.setText(R.string.wait_for_fix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderDisabled(String provider) {
|
public void onProviderDisabled(String provider) {
|
||||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||||
messageText.setText(R.string.status_enable_gps);
|
messageText.setText(R.string.status_enable_gps);
|
||||||
messagePane.setVisibility(View.VISIBLE);
|
messagePane.setVisibility(View.VISIBLE);
|
||||||
messagePane.setOnClickListener(MyTracksMap.this);
|
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();
|
screen.requestLayout();
|
||||||
}
|
}
|
||||||
|
showCurrentLocation();
|
||||||
|
} else {
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"MyTracksMap: Network location update received.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChanged(Location location) {
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||||
// Recalculate the variation if there was a jump in location > 1km:
|
switch (status) {
|
||||||
if (currentLocation == null ||
|
case LocationProvider.OUT_OF_SERVICE:
|
||||||
location.distanceTo(currentLocation) > 1000) {
|
case LocationProvider.TEMPORARILY_UNAVAILABLE:
|
||||||
setVariation(location);
|
haveGoodFix = false;
|
||||||
}
|
messagePane.setVisibility(View.VISIBLE);
|
||||||
currentLocation = location;
|
|
||||||
boolean haveGoodFixNow =
|
|
||||||
currentLocation.getAccuracy() < minRequiredAccuracy;
|
|
||||||
if (haveGoodFixNow != haveGoodFix) {
|
|
||||||
haveGoodFix = haveGoodFixNow;
|
|
||||||
messagePane.setVisibility(haveGoodFix ? View.GONE : View.VISIBLE);
|
|
||||||
screen.requestLayout();
|
screen.requestLayout();
|
||||||
}
|
break;
|
||||||
showCurrentLocation();
|
|
||||||
} else {
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"MyTracksMap: Network location update received.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
public void onSensorChanged(SensorEvent se) {
|
||||||
if (provider.equals(MyTracksConstants.GPS_PROVIDER)) {
|
synchronized (this) {
|
||||||
switch (status) {
|
float magneticHeading = se.values[0];
|
||||||
case LocationProvider.OUT_OF_SERVICE:
|
double heading = magneticHeading + variation;
|
||||||
case LocationProvider.TEMPORARILY_UNAVAILABLE:
|
if (mapOverlay.setHeading((float) heading)) {
|
||||||
haveGoodFix = false;
|
mapView.invalidate();
|
||||||
messagePane.setVisibility(View.VISIBLE);
|
|
||||||
screen.requestLayout();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
private final SensorEventListener sensorListener = new SensorEventListener() {
|
@Override
|
||||||
@Override
|
public void onAccuracyChanged(Sensor s, int accuracy) {
|
||||||
public void onSensorChanged(SensorEvent se) {
|
// do nothing
|
||||||
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) {
|
* Set the sampling frequency from the total number of points in the track.
|
||||||
// do nothing
|
*
|
||||||
}
|
* @param track The track to read the total number of points from.
|
||||||
};
|
*/
|
||||||
|
private void setSamplingFrequency(Track track) {
|
||||||
|
long totalLocations = track.getStopId() - track.getStartId();
|
||||||
|
|
||||||
private void reloadSharedPreferences(SharedPreferences sharedPreferences,
|
// Limit the number of map points readings.
|
||||||
String key) {
|
samplingFrequency =
|
||||||
if (key == null ||
|
(int) (1 +
|
||||||
key.equals(getString(R.string.min_required_accuracy_key))) {
|
totalLocations / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
minRequiredAccuracy = sharedPreferences.getInt(
|
Log.i(MyTracksConstants.TAG, "Sampling locations: " + samplingFrequency);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readAllNewTrackPoints() {
|
private void readAllNewTrackPoints() {
|
||||||
int numPoints = mapOverlay.getNumLocations();
|
Cursor cursor = null;
|
||||||
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
|
// Refetch the track to get the latest StopId
|
||||||
// We're about to exceed the maximum allowed number of points, so reload
|
selectedTrack = providerUtils.getTrack(selectedTrack.getId());
|
||||||
// the whole track with fewer points (the sampling frequency will be
|
long totalLocations = selectedTrack.getStopId() -
|
||||||
// lower).
|
selectedTrack.getStartId();
|
||||||
loadSelectedTrack();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Keep a copy of selectedTrackId, because it can change asynchronously.
|
|
||||||
long currentSelectedTrackId = selectedTrackId;
|
|
||||||
long lastStoredLocationId =
|
|
||||||
providerUtils.getLastLocationId(currentSelectedTrackId);
|
|
||||||
int samplingFrequency = -1;
|
|
||||||
Location location = new Location("");
|
|
||||||
while (currentSelectedTrackId == selectedTrackId) {
|
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
cursor = providerUtils.getLocationsCursor(currentSelectedTrackId,
|
|
||||||
lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false);
|
|
||||||
if (cursor == null || !cursor.moveToFirst()) {
|
|
||||||
// No (more) data
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
setSamplingFrequency(selectedTrack);
|
||||||
|
int bufferSize = 1024;
|
||||||
|
int points = 0;
|
||||||
|
while (lastSeenLocationId < selectedTrack.getStopId()) {
|
||||||
|
cursor = providerUtils.getLocationsCursor(
|
||||||
|
selectedTrack.getId(), lastSeenLocationId, bufferSize, false);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
final int idColumnIdx = cursor.getColumnIndexOrThrow(
|
final int idColumnIdx = cursor.getColumnIndexOrThrow(
|
||||||
TrackPointsColumns._ID);
|
TrackPointsColumns._ID);
|
||||||
do {
|
while (cursor.moveToNext()) {
|
||||||
long locationId = cursor.getLong(idColumnIdx);
|
points++;
|
||||||
lastSeenLocationId = locationId;
|
Location location = providerUtils.createLocation(cursor);
|
||||||
if (firstSeenLocationId == -1) {
|
lastSeenLocationId = cursor.getLong(idColumnIdx);
|
||||||
// This was our first point, keep its ID
|
|
||||||
firstSeenLocationId = locationId;
|
|
||||||
}
|
|
||||||
if (samplingFrequency == -1) {
|
|
||||||
// Now we already have at least one point, calculate the sampling
|
|
||||||
// frequency
|
|
||||||
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
|
||||||
samplingFrequency = (int) (1 + numTotalPoints
|
|
||||||
/ MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
|
||||||
// TODO: This shouldn't happen after adding currentSelectedTrackId,
|
|
||||||
// but just to be safe until we have 100% confidence.
|
|
||||||
if (samplingFrequency <= 0) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"readAllNewTrackPoints: samplingFreq <= 0, numTotalPoints = "
|
|
||||||
+ numTotalPoints + ", trackId = " + currentSelectedTrackId);
|
|
||||||
samplingFrequency = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
providerUtils.fillLocation(cursor, location);
|
|
||||||
|
|
||||||
// Include a point if it fits one of the following criteria:
|
// 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.
|
// - Is the last point and we are not recording this track.
|
||||||
// - The point is a segment split
|
if (!MyTracksUtils.isValidLocation(location) ||
|
||||||
if (numPoints % samplingFrequency == 0 ||
|
points % samplingFrequency == 0 ||
|
||||||
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
points == 0 ||
|
||||||
!MyTracksUtils.isValidLocation(location)) {
|
(recordingTrackId != selectedTrack.getId() &&
|
||||||
// No need to allocate a new location (we can safely reuse the existing).
|
points == (totalLocations - 1))) {
|
||||||
mapOverlay.addLocation(location);
|
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();
|
mapView.postInvalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,17 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
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.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
|
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapView;
|
import com.google.android.maps.MapView;
|
||||||
import com.google.android.maps.Overlay;
|
import com.google.android.maps.Overlay;
|
||||||
import com.google.android.maps.Projection;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
@@ -35,9 +35,6 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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
|
* 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 {
|
public class MyTracksOverlay extends Overlay {
|
||||||
|
|
||||||
private final Drawable[] arrows;
|
private final Drawable arrow[] = new Drawable[18];
|
||||||
private final int arrowWidth, arrowHeight;
|
private final int arrowWidth, arrowHeight;
|
||||||
private final Drawable statsMarker;
|
private final Drawable statsMarker;
|
||||||
private final Drawable waypointMarker;
|
private final Drawable waypointMarker;
|
||||||
@@ -57,152 +54,102 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
private final Paint selectedTrackPaint;
|
private final Paint selectedTrackPaint;
|
||||||
private final Paint errorCirclePaint;
|
private final Paint errorCirclePaint;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final List<Waypoint> waypoints;
|
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||||
private final List<CachedLocation> points;
|
private final ArrayList<Location> points = new ArrayList<Location>(1024);
|
||||||
private final BlockingQueue<CachedLocation> pendingPoints;
|
|
||||||
|
|
||||||
private boolean trackDrawingEnabled;
|
private Track selectedTrack;
|
||||||
private int lastHeading = 0;
|
private int lastHeading = 0;
|
||||||
private Location myLocation;
|
private Location myLocation;
|
||||||
private boolean showEndMarker = true;
|
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) {
|
public MyTracksOverlay(Context context) {
|
||||||
this.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
|
// TODO: Can we use a FrameAnimation or similar here rather
|
||||||
// than individual resources for each arrow direction?
|
// than individual resources for each arrow direction?
|
||||||
final Resources resources = context.getResources();
|
|
||||||
arrows = new Drawable[] {
|
arrow[0] = context.getResources().getDrawable(R.drawable.arrow_0);
|
||||||
resources.getDrawable(R.drawable.arrow_0),
|
arrow[1] = context.getResources().getDrawable(R.drawable.arrow_20);
|
||||||
resources.getDrawable(R.drawable.arrow_20),
|
arrow[2] = context.getResources().getDrawable(R.drawable.arrow_40);
|
||||||
resources.getDrawable(R.drawable.arrow_40),
|
arrow[3] = context.getResources().getDrawable(R.drawable.arrow_60);
|
||||||
resources.getDrawable(R.drawable.arrow_60),
|
arrow[4] = context.getResources().getDrawable(R.drawable.arrow_80);
|
||||||
resources.getDrawable(R.drawable.arrow_80),
|
arrow[5] = context.getResources().getDrawable(R.drawable.arrow_100);
|
||||||
resources.getDrawable(R.drawable.arrow_100),
|
arrow[6] = context.getResources().getDrawable(R.drawable.arrow_120);
|
||||||
resources.getDrawable(R.drawable.arrow_120),
|
arrow[7] = context.getResources().getDrawable(R.drawable.arrow_140);
|
||||||
resources.getDrawable(R.drawable.arrow_140),
|
arrow[8] = context.getResources().getDrawable(R.drawable.arrow_160);
|
||||||
resources.getDrawable(R.drawable.arrow_160),
|
arrow[9] = context.getResources().getDrawable(R.drawable.arrow_180);
|
||||||
resources.getDrawable(R.drawable.arrow_180),
|
arrow[10] = context.getResources().getDrawable(R.drawable.arrow_200);
|
||||||
resources.getDrawable(R.drawable.arrow_200),
|
arrow[11] = context.getResources().getDrawable(R.drawable.arrow_220);
|
||||||
resources.getDrawable(R.drawable.arrow_220),
|
arrow[12] = context.getResources().getDrawable(R.drawable.arrow_240);
|
||||||
resources.getDrawable(R.drawable.arrow_240),
|
arrow[13] = context.getResources().getDrawable(R.drawable.arrow_260);
|
||||||
resources.getDrawable(R.drawable.arrow_260),
|
arrow[14] = context.getResources().getDrawable(R.drawable.arrow_280);
|
||||||
resources.getDrawable(R.drawable.arrow_280),
|
arrow[15] = context.getResources().getDrawable(R.drawable.arrow_300);
|
||||||
resources.getDrawable(R.drawable.arrow_300),
|
arrow[16] = context.getResources().getDrawable(R.drawable.arrow_320);
|
||||||
resources.getDrawable(R.drawable.arrow_320),
|
arrow[17] = context.getResources().getDrawable(R.drawable.arrow_340);
|
||||||
resources.getDrawable(R.drawable.arrow_340)
|
arrowWidth = arrow[lastHeading].getIntrinsicWidth();
|
||||||
};
|
arrowHeight = arrow[lastHeading].getIntrinsicHeight();
|
||||||
arrowWidth = arrows[lastHeading].getIntrinsicWidth();
|
for (int i = 0; i <= 17; i++) {
|
||||||
arrowHeight = arrows[lastHeading].getIntrinsicHeight();
|
arrow[i].setBounds(0, 0, arrowWidth, arrowHeight);
|
||||||
for (Drawable arrow : arrows) {
|
|
||||||
arrow.setBounds(0, 0, arrowWidth, arrowHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statsMarker = resources.getDrawable(R.drawable.ylw_pushpin);
|
statsMarker = context.getResources().getDrawable(R.drawable.ylw_pushpin);
|
||||||
markerWidth = statsMarker.getIntrinsicWidth();
|
markerWidth = statsMarker.getIntrinsicWidth();
|
||||||
markerHeight = statsMarker.getIntrinsicHeight();
|
markerHeight = statsMarker.getIntrinsicHeight();
|
||||||
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
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);
|
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);
|
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);
|
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
selectedTrackPaint = new Paint();
|
selectedTrackPaint = new Paint();
|
||||||
selectedTrackPaint.setColor(resources.getColor(R.color.red));
|
selectedTrackPaint.setColor(context.getResources().getColor(R.color.red));
|
||||||
selectedTrackPaint.setStrokeWidth(3);
|
selectedTrackPaint.setStrokeWidth(3);
|
||||||
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
||||||
selectedTrackPaint.setAntiAlias(true);
|
selectedTrackPaint.setAntiAlias(true);
|
||||||
|
|
||||||
errorCirclePaint = new Paint();
|
errorCirclePaint = new Paint();
|
||||||
errorCirclePaint.setColor(resources.getColor(R.color.blue));
|
errorCirclePaint.setColor(context.getResources().getColor(R.color.blue));
|
||||||
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
||||||
errorCirclePaint.setStrokeWidth(3);
|
errorCirclePaint.setStrokeWidth(3);
|
||||||
errorCirclePaint.setAlpha(127);
|
errorCirclePaint.setAlpha(127);
|
||||||
errorCirclePaint.setAntiAlias(true);
|
errorCirclePaint.setAntiAlias(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelectedTrack(Track track) {
|
||||||
|
selectedTrack = track;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Track getSelectedTrack() {
|
||||||
|
return selectedTrack;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a location to the map overlay.
|
* Add a location to the map overlay.
|
||||||
*
|
*
|
||||||
* NOTE: This method doesn't take ownership of the given location, so it is
|
* NOTE: this method takes ownership of this location and may change it.
|
||||||
* safe to reuse the same location while calling this method.
|
|
||||||
*
|
*
|
||||||
* @param l the location to add.
|
* @param l the location to add
|
||||||
*/
|
*/
|
||||||
public void addLocation(Location l) {
|
public void addLocation(Location l) {
|
||||||
// Queue up in the pending queue until it's merged with {@code #points}.
|
points.add(l);
|
||||||
pendingPoints.offer(new CachedLocation(l));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWaypoint(Waypoint wpt) {
|
public void addWaypoint(Waypoint wpt) {
|
||||||
// Note: We don't cache waypoints, because it's not worth the effort.
|
waypoints.add(wpt);
|
||||||
if (wpt != null && wpt.getLocation() != null) {
|
|
||||||
synchronized (waypoints) {
|
|
||||||
waypoints.add(wpt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumLocations() {
|
|
||||||
synchronized (points) {
|
|
||||||
return points.size() + pendingPoints.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
int getNumWaypoints() {
|
|
||||||
synchronized (waypoints) {
|
|
||||||
return waypoints.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearPoints() {
|
|
||||||
synchronized (points) {
|
|
||||||
points.clear();
|
|
||||||
pendingPoints.clear();
|
|
||||||
lastPath = null;
|
|
||||||
lastViewRect = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearWaypoints() {
|
public void clearWaypoints() {
|
||||||
synchronized (waypoints) {
|
waypoints.clear();
|
||||||
waypoints.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrackDrawingEnabled(boolean trackDrawingEnabled) {
|
public void clearPoints() {
|
||||||
this.trackDrawingEnabled = trackDrawingEnabled;
|
points.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShowEndMarker(boolean showEndMarker) {
|
public void setShowEndMarker(boolean showEndMarker) {
|
||||||
@@ -211,179 +158,159 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
public void draw(Canvas canvas, MapView mapView, boolean shadow) {
|
||||||
|
|
||||||
if (shadow) {
|
if (shadow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's safe to keep projection within a single draw operation.
|
// Draw the selected track:
|
||||||
final Projection projection = getMapProjection(mapView);
|
drawTrack(canvas, mapView, selectedTrack, true);
|
||||||
// Get the current viewing window.
|
|
||||||
if (trackDrawingEnabled) {
|
|
||||||
Rect viewRect = getMapViewRect(mapView);
|
|
||||||
|
|
||||||
// Draw the selected track:
|
// Draw the waypoints:
|
||||||
drawTrack(canvas, projection, viewRect);
|
ArrayList<Waypoint> currentWaypoints = waypoints;
|
||||||
|
for (int i = 1; i < currentWaypoints.size(); i++) {
|
||||||
// Draw the waypoints:
|
Waypoint wpt = currentWaypoints.get(i);
|
||||||
drawWaypoints(canvas, projection);
|
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
|
// Draw the arrow icon:
|
||||||
drawMyLocation(canvas, projection);
|
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.
|
public void drawTrack(Canvas canvas, MapView mapView, Track track,
|
||||||
Projection getMapProjection(MapView mapView) {
|
boolean selected) {
|
||||||
return mapView.getProjection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
if (track == null) {
|
||||||
Rect getMapViewRect(MapView mapView) {
|
return;
|
||||||
|
}
|
||||||
|
if (points.size() < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current viewing window:
|
||||||
int w = mapView.getLongitudeSpan();
|
int w = mapView.getLongitudeSpan();
|
||||||
int h = mapView.getLatitudeSpan();
|
int h = mapView.getLatitudeSpan();
|
||||||
int cx = mapView.getMapCenter().getLongitudeE6();
|
int cx = mapView.getMapCenter().getLongitudeE6();
|
||||||
int cy = mapView.getMapCenter().getLatitudeE6();
|
int cy = mapView.getMapCenter().getLatitudeE6();
|
||||||
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
Rect rect = new Rect(cx - w, cy - h, cx + w, cy + h);
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
Point pt = new Point();
|
||||||
Path newPath() {
|
GeoPoint geoPoint;
|
||||||
return new Path();
|
Location loc;
|
||||||
}
|
Location lastValidLocation = null;
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
Path getLastPath() {
|
|
||||||
return lastPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
Path path;
|
Path path;
|
||||||
synchronized (points) {
|
boolean wasInside;
|
||||||
// Merge the pending points with the list of cached locations.
|
|
||||||
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
// Do as much allocation and preparation outside the loop over track
|
||||||
int newPoints = pendingPoints.drainTo(points);
|
// points:
|
||||||
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
wasInside = false;
|
||||||
!referencePoint.equals(lastReferencePoint);
|
int locLon = 0, locLat = 0, minLon, maxLon, minLat, maxLat;
|
||||||
if (newPoints == 0 && lastPath != null && !newProjection) {
|
int lastLocLon = (int) (points.get(0).getLongitude() * 1E6);
|
||||||
// No need to recreate path (same points and viewing area).
|
int lastLocLat = (int) (points.get(0).getLatitude() * 1E6);
|
||||||
path = lastPath;
|
|
||||||
|
// Loop over track points:
|
||||||
|
path = new Path();
|
||||||
|
for (int i = 1; i < points.size(); i++) {
|
||||||
|
|
||||||
|
loc = points.get(i);
|
||||||
|
if (loc == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
locLon = (int) (loc.getLongitude() * 1E6);
|
||||||
|
locLat = (int) (loc.getLatitude() * 1E6);
|
||||||
|
|
||||||
|
// Draw a line segment if it's inside the viewing window:
|
||||||
|
if (locLat < 90E6 && lastLocLat < 90E6) {
|
||||||
|
lastValidLocation = loc;
|
||||||
|
minLon = Math.min(locLon, lastLocLon);
|
||||||
|
maxLon = Math.max(locLon, lastLocLon);
|
||||||
|
minLat = Math.min(locLat, lastLocLat);
|
||||||
|
maxLat = Math.max(locLat, lastLocLat);
|
||||||
|
if (rect.intersects(minLon, minLat, maxLon, maxLat)) {
|
||||||
|
if (!wasInside) {
|
||||||
|
geoPoint = new GeoPoint(lastLocLat, lastLocLon);
|
||||||
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
path.moveTo(pt.x, pt.y);
|
||||||
|
}
|
||||||
|
geoPoint = new GeoPoint(locLat, locLon);
|
||||||
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
path.lineTo(pt.x, pt.y);
|
||||||
|
wasInside = rect.contains(locLon, locLat);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int numPoints = points.size();
|
wasInside = false;
|
||||||
if (numPoints < 2) {
|
|
||||||
// Not enough points to draw a path.
|
|
||||||
path = null;
|
|
||||||
} else if (lastPath != null && !newProjection) {
|
|
||||||
// Incremental update of the path, without repositioning the view.
|
|
||||||
path = lastPath;
|
|
||||||
updatePath(projection, viewRect, path, numPoints - newPoints);
|
|
||||||
} else {
|
|
||||||
// The view has changed so we have to start from scratch.
|
|
||||||
path = newPath();
|
|
||||||
path.incReserve(numPoints);
|
|
||||||
updatePath(projection, viewRect, path, 0);
|
|
||||||
}
|
|
||||||
lastPath = path;
|
|
||||||
}
|
}
|
||||||
lastReferencePoint = referencePoint;
|
lastLocLon = locLon;
|
||||||
lastViewRect = viewRect;
|
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.
|
// Draw the "Start" marker:
|
||||||
if (showEndMarker) {
|
for (int i = 0; i < points.size(); i++) {
|
||||||
for (int i = points.size() - 1; i >= 0; --i) {
|
loc = points.get(i);
|
||||||
if (points.get(i).valid) {
|
if (loc.getLatitude() < 90) {
|
||||||
drawElement(canvas, projection, points.get(i).geoPoint, endMarker,
|
locLon = (int) (loc.getLongitude() * 1E6);
|
||||||
-markerWidth / 2, -markerHeight);
|
locLat = (int) (loc.getLatitude() * 1E6);
|
||||||
break;
|
geoPoint = new GeoPoint(locLat, locLon);
|
||||||
}
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
}
|
canvas.save();
|
||||||
}
|
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
||||||
|
startMarker.draw(canvas);
|
||||||
// Draw the "Start" marker.
|
canvas.restore();
|
||||||
for (int i = 0; i < points.size(); ++i) {
|
|
||||||
if (points.get(i).valid) {
|
|
||||||
drawElement(canvas, projection, points.get(i).geoPoint, startMarker,
|
|
||||||
-markerWidth / 2, -markerHeight);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePath(Projection projection, Rect viewRect, Path path,
|
@Override
|
||||||
int startLocationIdx) {
|
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
|
||||||
// Whether to start a new segment on new valid and visible point.
|
long when) {
|
||||||
boolean newSegment =
|
draw(canvas, mapView, shadow);
|
||||||
startLocationIdx > 0 ? !points.get(startLocationIdx - 1).valid : true;
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -426,22 +353,20 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
final Location tapLocation = MyTracksUtils.getLocation(p);
|
final Location tapLocation = MyTracksUtils.getLocation(p);
|
||||||
double dmin = Double.MAX_VALUE;
|
double dmin = Double.MAX_VALUE;
|
||||||
Waypoint waypoint = null;
|
Waypoint waypoint = null;
|
||||||
synchronized (waypoints) {
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
if (waypointLocation == null) {
|
||||||
if (waypointLocation == null) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
final double d = waypointLocation.distanceTo(tapLocation);
|
||||||
final double d = waypointLocation.distanceTo(tapLocation);
|
if (d < dmin) {
|
||||||
if (d < dmin) {
|
dmin = d;
|
||||||
dmin = d;
|
waypoint = waypoints.get(i);
|
||||||
waypoint = waypoints.get(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waypoint != null &&
|
if (waypoint != null
|
||||||
dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
&& dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||||
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
||||||
intent.putExtra("waypointid", waypoint.getId());
|
intent.putExtra("waypointid", waypoint.getId());
|
||||||
context.startActivity(intent);
|
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;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
||||||
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.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
|
import android.preference.Preference.OnPreferenceChangeListener;
|
||||||
import android.preference.PreferenceActivity;
|
import android.preference.PreferenceActivity;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.Preference.OnPreferenceChangeListener;
|
import android.util.Log;
|
||||||
import android.preference.Preference.OnPreferenceClickListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity that let's the user see and edit the settings.
|
* 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.
|
* 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_ANNOUNCEMENT_FREQUENCY = -1;
|
||||||
public static final int DEFAULT_MAX_RECORDING_DISTANCE = 200;
|
public static final int DEFAULT_MAX_RECORDING_DISTANCE = 200;
|
||||||
public static final int DEFAULT_MIN_RECORDING_DISTANCE = 5;
|
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_MIN_REQUIRED_ACCURACY = 200;
|
||||||
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
||||||
|
|
||||||
private BackupPreferencesListener backupListener;
|
private static boolean mTTSAvailable;
|
||||||
private SharedPreferences preferences;
|
|
||||||
|
/* establish whether the tts class is available to us */
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
SafeStatusAnnouncerTask.checkAvailable();
|
||||||
|
mTTSAvailable = true;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.d(MyTracksConstants.TAG, "TTS not available.", t);
|
||||||
|
mTTSAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(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
|
// Tell it where to read/write preferences
|
||||||
PreferenceManager preferenceManager = getPreferenceManager();
|
PreferenceManager preferenceManager = getPreferenceManager();
|
||||||
preferenceManager.setSharedPreferencesName(SETTINGS_NAME);
|
preferenceManager.setSharedPreferencesName(SETTINGS_NAME);
|
||||||
preferenceManager.setSharedPreferencesMode(0);
|
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
|
// Load the preferences to be displayed
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
addPreferencesFromResource(R.xml.preferences);
|
||||||
|
|
||||||
// Hook up switching of displayed list entries between metric and imperial
|
// Hook up switching of displayed list entries between metric and imperial
|
||||||
// units
|
// units
|
||||||
CheckBoxPreference metricUnitsPreference =
|
CheckBoxPreference metricUnitsPreference =
|
||||||
(CheckBoxPreference) findPreference(
|
(CheckBoxPreference) findPreference(getString(R.string.metric_units_key));
|
||||||
getString(R.string.metric_units_key));
|
|
||||||
metricUnitsPreference.setOnPreferenceChangeListener(
|
metricUnitsPreference.setOnPreferenceChangeListener(
|
||||||
new OnPreferenceChangeListener() {
|
new OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -97,70 +90,17 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
||||||
|
|
||||||
// Disable TTS announcement preference if not available
|
// Disable TTS announcement preference if not available
|
||||||
if (!apiFeatures.hasTextToSpeech()) {
|
if (!mTTSAvailable) {
|
||||||
IntegerListPreference announcementFrequency =
|
IntegerListPreference announcementFrequency =
|
||||||
(IntegerListPreference) findPreference(
|
(IntegerListPreference) findPreference(
|
||||||
getString(R.string.announcement_frequency_key));
|
getString(R.string.announcement_frequency_key));
|
||||||
announcementFrequency.setEnabled(false);
|
announcementFrequency.setEnabled(false);
|
||||||
announcementFrequency.setValue("-1");
|
announcementFrequency.setValue("-1");
|
||||||
announcementFrequency.setSummary(
|
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
|
* Updates all the preferences which give options with distance units to use
|
||||||
* the proper unit the user has selected.
|
* the proper unit the user has selected.
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ public class MyTracksWaypointDetails extends Activity
|
|||||||
iconId = R.drawable.ylw_pushpin;
|
iconId = R.drawable.ylw_pushpin;
|
||||||
TripStatistics waypointStats = waypoint.getStatistics();
|
TripStatistics waypointStats = waypoint.getStatistics();
|
||||||
utils.setAllStats(waypointStats);
|
utils.setAllStats(waypointStats);
|
||||||
|
utils.setTime(R.id.total_time_register, waypointStats.getTotalTime());
|
||||||
utils.setAltitude(
|
utils.setAltitude(
|
||||||
R.id.elevation_register, waypoint.getLocation().getAltitude());
|
R.id.elevation_register, waypoint.getLocation().getAltitude());
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -49,18 +50,21 @@ public class NavControls {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A touchable image view.
|
* A touchable image view.
|
||||||
* When touched it changes the navigation control icons accordingly.
|
|
||||||
*/
|
*/
|
||||||
private class TouchLayout extends RelativeLayout implements Runnable {
|
public class TouchLayout extends RelativeLayout {
|
||||||
private final boolean isLeft;
|
private final ImageView arrow;
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
|
|
||||||
public TouchLayout(Context context, boolean isLeft) {
|
public TouchLayout(Context context, boolean isLeft) {
|
||||||
super(context);
|
super(context);
|
||||||
this.isLeft = isLeft;
|
arrow = new ImageView(context);
|
||||||
this.icon = 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);
|
icon.setVisibility(View.GONE);
|
||||||
|
addView(arrow);
|
||||||
addView(icon);
|
addView(icon);
|
||||||
|
icon.setPadding((isLeft ? 15 : 10), 27, 15, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIcon(Drawable drawable) {
|
public void setIcon(Drawable drawable) {
|
||||||
@@ -73,11 +77,18 @@ public class NavControls {
|
|||||||
switch (event.getAction()) {
|
switch (event.getAction()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
setPressed(true);
|
setPressed(true);
|
||||||
shiftIcons(isLeft);
|
hide();
|
||||||
|
if (this == prevImage) {
|
||||||
// Call the user back
|
if (prevRunnable != null) {
|
||||||
handler.post(this);
|
handler.post(prevRunnable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (this == nextImage) {
|
||||||
|
if (nextRunnable != null) {
|
||||||
|
handler.post(nextRunnable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_UP:
|
case MotionEvent.ACTION_UP:
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
@@ -85,69 +96,55 @@ public class NavControls {
|
|||||||
}
|
}
|
||||||
return super.onTouchEvent(event);
|
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 Handler handler = new Handler();
|
||||||
private final Runnable dismissControls = new Runnable() {
|
private final Runnable dismissControls = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final TouchLayout prevImage;
|
private final TouchLayout prevImage;
|
||||||
private final TouchLayout nextImage;
|
private final TouchLayout nextImage;
|
||||||
private final TypedArray leftIcons;
|
|
||||||
private final TypedArray rightIcons;
|
|
||||||
private final Runnable touchRunnable;
|
|
||||||
|
|
||||||
private boolean isVisible = false;
|
private boolean isVisible = false;
|
||||||
private int currentIcons;
|
private boolean hasNext = true;
|
||||||
|
private boolean hasPrev = true;
|
||||||
|
|
||||||
public NavControls(Context context, ViewGroup container,
|
public NavControls(Context context, ViewGroup container,
|
||||||
TypedArray leftIcons, TypedArray rightIcons,
|
Runnable prevRunnable, Runnable nextRunnable) {
|
||||||
Runnable touchRunnable) {
|
this.context = context;
|
||||||
this.leftIcons = leftIcons;
|
this.prevRunnable = prevRunnable;
|
||||||
this.rightIcons = rightIcons;
|
this.nextRunnable = nextRunnable;
|
||||||
this.touchRunnable = touchRunnable;
|
|
||||||
|
|
||||||
if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid icons specified");
|
|
||||||
}
|
|
||||||
if (touchRunnable == null) {
|
|
||||||
throw new NullPointerException("Runnable cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
LayoutParams prevParams = new LayoutParams(
|
LayoutParams prevParams = new LayoutParams(
|
||||||
LayoutParams.WRAP_CONTENT,
|
LayoutParams.WRAP_CONTENT,
|
||||||
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 nextParams = new LayoutParams(
|
||||||
LayoutParams.WRAP_CONTENT,
|
LayoutParams.WRAP_CONTENT,
|
||||||
LayoutParams.WRAP_CONTENT);
|
LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
|
||||||
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||||
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
|
||||||
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
|
||||||
|
|
||||||
nextImage = new TouchLayout(context, false);
|
nextImage = new TouchLayout(context, false);
|
||||||
prevImage = new TouchLayout(context, true);
|
|
||||||
nextImage.setLayoutParams(nextParams);
|
nextImage.setLayoutParams(nextParams);
|
||||||
prevImage.setLayoutParams(prevParams);
|
|
||||||
nextImage.setVisibility(View.INVISIBLE);
|
nextImage.setVisibility(View.INVISIBLE);
|
||||||
prevImage.setVisibility(View.INVISIBLE);
|
|
||||||
|
|
||||||
container.addView(prevImage);
|
|
||||||
container.addView(nextImage);
|
container.addView(nextImage);
|
||||||
|
}
|
||||||
|
|
||||||
prevImage.setIcon(leftIcons.getDrawable(0));
|
public void setHasNext(boolean hasNext) {
|
||||||
nextImage.setIcon(rightIcons.getDrawable(0));
|
this.hasNext = hasNext;
|
||||||
this.currentIcons = 0;
|
}
|
||||||
|
|
||||||
|
public void setHasPrev(boolean hasPrev) {
|
||||||
|
this.hasPrev = hasPrev;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void keepVisible() {
|
private void keepVisible() {
|
||||||
@@ -159,18 +156,20 @@ public class NavControls {
|
|||||||
|
|
||||||
public void show() {
|
public void show() {
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
SHOW_PREV_ANIMATION.setDuration(500);
|
if (prevRunnable != null && hasPrev) {
|
||||||
SHOW_PREV_ANIMATION.startNow();
|
SHOW_PREV_ANIMATION.setDuration(500);
|
||||||
prevImage.setPressed(false);
|
SHOW_PREV_ANIMATION.startNow();
|
||||||
prevImage.setAnimation(SHOW_PREV_ANIMATION);
|
prevImage.setPressed(false);
|
||||||
prevImage.setVisibility(View.VISIBLE);
|
prevImage.setAnimation(SHOW_PREV_ANIMATION);
|
||||||
|
prevImage.setVisibility(View.VISIBLE);
|
||||||
SHOW_NEXT_ANIMATION.setDuration(500);
|
}
|
||||||
SHOW_NEXT_ANIMATION.startNow();
|
if (nextRunnable != null && hasNext) {
|
||||||
nextImage.setPressed(false);
|
SHOW_NEXT_ANIMATION.setDuration(500);
|
||||||
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
|
SHOW_NEXT_ANIMATION.startNow();
|
||||||
nextImage.setVisibility(View.VISIBLE);
|
nextImage.setPressed(false);
|
||||||
|
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
|
||||||
|
nextImage.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
isVisible = true;
|
isVisible = true;
|
||||||
keepVisible();
|
keepVisible();
|
||||||
} else {
|
} else {
|
||||||
@@ -189,26 +188,33 @@ public class NavControls {
|
|||||||
|
|
||||||
public void hide() {
|
public void hide() {
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
prevImage.setAnimation(HIDE_PREV_ANIMATION);
|
if (prevRunnable != null) {
|
||||||
HIDE_PREV_ANIMATION.setDuration(500);
|
if (hasPrev) {
|
||||||
HIDE_PREV_ANIMATION.startNow();
|
prevImage.setAnimation(HIDE_PREV_ANIMATION);
|
||||||
prevImage.setVisibility(View.INVISIBLE);
|
HIDE_PREV_ANIMATION.setDuration(500);
|
||||||
|
HIDE_PREV_ANIMATION.startNow();
|
||||||
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
|
} else {
|
||||||
HIDE_NEXT_ANIMATION.setDuration(500);
|
prevImage.clearAnimation();
|
||||||
HIDE_NEXT_ANIMATION.startNow();
|
}
|
||||||
nextImage.setVisibility(View.INVISIBLE);
|
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() {
|
public void setLeftIcon(int resourceId) {
|
||||||
return currentIcons;
|
prevImage.setIcon(context.getResources().getDrawable(resourceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shiftIcons(boolean isLeft) {
|
public void setRightIcon(int resourceId) {
|
||||||
// Increment or decrement by one, with wrap around
|
nextImage.setIcon(context.getResources().getDrawable(resourceId));
|
||||||
currentIcons = (currentIcons + leftIcons.length() +
|
|
||||||
(isLeft ? -1 : 1)) % leftIcons.length();
|
|
||||||
prevImage.setIcon(leftIcons.getDrawable(currentIcons));
|
|
||||||
nextImage.setIcon(rightIcons.getDrawable(currentIcons));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ package com.google.android.apps.mytracks;
|
|||||||
*/
|
*/
|
||||||
public interface ProgressIndicator {
|
public interface ProgressIndicator {
|
||||||
|
|
||||||
public void setProgressMessage(int resId);
|
public void setProgressMessage(String text);
|
||||||
public void clearProgressMessage();
|
|
||||||
|
|
||||||
public void setProgressValue(int percent);
|
public void setProgressValue(int percent);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class SendToGoogleDialog extends Dialog {
|
|||||||
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
|
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
|
||||||
cancel.setOnClickListener(new View.OnClickListener() {
|
cancel.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
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.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
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 com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -50,12 +48,14 @@ import android.widget.Toast;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class StatsActivity extends Activity
|
public class StatsActivity extends Activity
|
||||||
implements OnSharedPreferenceChangeListener {
|
implements OnSharedPreferenceChangeListener, LocationListener {
|
||||||
|
|
||||||
private StatsUtilities utils;
|
|
||||||
|
private final StatsUtilities utils;
|
||||||
private UIUpdateThread thread;
|
private UIUpdateThread thread;
|
||||||
|
|
||||||
private ContentObserver observer;
|
private ContentObserver observer;
|
||||||
|
private LocationManager locationManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the currently selected track.
|
* The id of the currently selected track.
|
||||||
@@ -94,7 +94,8 @@ public class StatsActivity extends Activity
|
|||||||
*/
|
*/
|
||||||
private boolean showCurrentSegment = false;
|
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.
|
* 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. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(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:
|
// We don't need a window title bar:
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
@@ -177,9 +175,11 @@ public class StatsActivity extends Activity
|
|||||||
preferences.getBoolean(getString(R.string.report_speed_key), true);
|
preferences.getBoolean(getString(R.string.report_speed_key), true);
|
||||||
checkLiveTrack();
|
checkLiveTrack();
|
||||||
restoreStats();
|
restoreStats();
|
||||||
showUnknownLocation();
|
updateLocation(null);
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
|
locationManager =
|
||||||
|
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
||||||
utils.setMetricUnits(metricUnits);
|
utils.setMetricUnits(metricUnits);
|
||||||
utils.setReportSpeed(displaySpeed);
|
utils.setReportSpeed(displaySpeed);
|
||||||
utils.updateUnits();
|
utils.updateUnits();
|
||||||
@@ -210,57 +210,49 @@ public class StatsActivity extends Activity
|
|||||||
activityOnTop = true;
|
activityOnTop = true;
|
||||||
checkLiveTrack();
|
checkLiveTrack();
|
||||||
restoreStats();
|
restoreStats();
|
||||||
showUnknownLocation();
|
updateLocation(null);
|
||||||
super.onResume();
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(
|
public void onSharedPreferenceChanged(
|
||||||
final SharedPreferences sharedPreferences, final String key) {
|
SharedPreferences sharedPreferences, String key) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"StatsActivity: onSharedPreferences changed " + key);
|
"StatsActivity: onSharedPreferences changed " + key);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
runOnUiThread(new Runnable() {
|
if (key.equals(getString(R.string.selected_track_key))) {
|
||||||
@Override
|
selectedTrackId =
|
||||||
public void run() {
|
sharedPreferences.getLong(getString(R.string.selected_track_key),
|
||||||
if (key.equals(getString(R.string.selected_track_key))) {
|
-1);
|
||||||
selectedTrackId =
|
checkLiveTrack();
|
||||||
sharedPreferences.getLong(
|
restoreStats();
|
||||||
getString(R.string.selected_track_key),
|
updateLocation(null);
|
||||||
-1);
|
} else if (key.equals(getString(R.string.recording_track_key))) {
|
||||||
checkLiveTrack();
|
recordingTrackId =
|
||||||
restoreStats();
|
sharedPreferences.getLong(getString(R.string.recording_track_key),
|
||||||
showUnknownLocation();
|
-1);
|
||||||
} else if (key.equals(getString(R.string.recording_track_key))) {
|
checkLiveTrack();
|
||||||
recordingTrackId =
|
restoreStats();
|
||||||
sharedPreferences.getLong(
|
updateLocation(null);
|
||||||
getString(R.string.recording_track_key),
|
} else if (key.equals(getString(R.string.metric_units_key))) {
|
||||||
-1);
|
metricUnits =
|
||||||
checkLiveTrack();
|
sharedPreferences.getBoolean(
|
||||||
restoreStats();
|
getString(R.string.metric_units_key), true);
|
||||||
showUnknownLocation();
|
utils.setMetricUnits(metricUnits);
|
||||||
} else if (key.equals(getString(R.string.metric_units_key))) {
|
utils.updateUnits();
|
||||||
metricUnits =
|
restoreStats();
|
||||||
sharedPreferences.getBoolean(
|
} else if (key.equals(getString(R.string.report_speed_key))) {
|
||||||
getString(R.string.metric_units_key), true);
|
displaySpeed =
|
||||||
utils.setMetricUnits(metricUnits);
|
sharedPreferences.getBoolean(getString(R.string.report_speed_key),
|
||||||
utils.updateUnits();
|
true);
|
||||||
restoreStats();
|
utils.setReportSpeed(displaySpeed);
|
||||||
} else if (key.equals(getString(R.string.report_speed_key))) {
|
utils.updateUnits();
|
||||||
displaySpeed =
|
utils.setSpeedLabel(
|
||||||
sharedPreferences.getBoolean(
|
R.id.speed_label, R.string.speed, R.string.pace_label);
|
||||||
getString(R.string.report_speed_key),
|
Log.w(MyTracksConstants.TAG, "Setting speed labels");
|
||||||
true);
|
utils.setSpeedLabels();
|
||||||
utils.setReportSpeed(displaySpeed);
|
restoreStats();
|
||||||
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);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LocationListener locationListener = new LocationListener() {
|
@Override
|
||||||
@Override
|
public void onLocationChanged(Location l) {
|
||||||
public void onLocationChanged(Location l) {
|
if (selectedTrackIsRecording()) {
|
||||||
if (selectedTrackIsRecording()) {
|
updateLocation(l);
|
||||||
showLocation(l);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderDisabled(String provider) {
|
public void onProviderDisabled(String provider) {
|
||||||
// Do nothing
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderEnabled(String provider) {
|
public void onProviderEnabled(String provider) {
|
||||||
// Do nothing
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
// Do nothing
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers to receive location updates from the GPS location provider.
|
* Registers to receive location updates from the GPS location provider.
|
||||||
*/
|
*/
|
||||||
private void registerLocationListener() {
|
private void registerLocationListener() {
|
||||||
LocationManager locationManager =
|
|
||||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
if (locationManager != null) {
|
if (locationManager != null) {
|
||||||
LocationProvider gpsProvider =
|
LocationProvider gpsProvider =
|
||||||
locationManager.getProvider(MyTracksConstants.GPS_PROVIDER);
|
locationManager.getProvider(MyTracksConstants.GPS_PROVIDER);
|
||||||
@@ -337,7 +322,7 @@ public class StatsActivity extends Activity
|
|||||||
+ gpsProvider.getName());
|
+ gpsProvider.getName());
|
||||||
}
|
}
|
||||||
locationManager.requestLocationUpdates(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.
|
* Unregisters all location listener.
|
||||||
*/
|
*/
|
||||||
private void unregisterLocationListener() {
|
private void unregisterLocationListener() {
|
||||||
LocationManager locationManager =
|
|
||||||
(LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
if (locationManager != null) {
|
if (locationManager != null) {
|
||||||
locationManager.removeUpdates(locationListener);
|
locationManager.removeUpdates(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,14 +352,9 @@ public class StatsActivity extends Activity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track = providerUtils.getTrack(selectedTrackId);
|
track = providerUtils.getTrack(selectedTrackId);
|
||||||
if (track == null || track.getStatistics() == null) {
|
|
||||||
utils.setAllToUnknown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime = track.getStatistics().getStartTime();
|
startTime = track.getStatistics().getStartTime();
|
||||||
if (!selectedTrackIsRecording()) {
|
if(!selectedTrackIsRecording()) {
|
||||||
utils.setTime(R.id.total_time_register,
|
utils.setTime(R.id.total_time_register,
|
||||||
track.getStatistics().getTotalTime());
|
track.getStatistics().getTotalTime());
|
||||||
}
|
}
|
||||||
@@ -426,17 +404,17 @@ public class StatsActivity extends Activity
|
|||||||
*
|
*
|
||||||
* @param l may be null (will set location fields to unknown)
|
* @param l may be null (will set location fields to unknown)
|
||||||
*/
|
*/
|
||||||
private void showLocation(Location l) {
|
private void updateLocation(Location l) {
|
||||||
utils.setAltitude(R.id.elevation_register, l.getAltitude());
|
if (l == null) {
|
||||||
utils.setLatLong(R.id.latitude_register, l.getLatitude());
|
utils.setUnknown(R.id.elevation_register);
|
||||||
utils.setLatLong(R.id.longitude_register, l.getLongitude());
|
utils.setUnknown(R.id.latitude_register);
|
||||||
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
|
utils.setUnknown(R.id.longitude_register);
|
||||||
}
|
utils.setUnknown(R.id.speed_register);
|
||||||
|
} else {
|
||||||
private void showUnknownLocation() {
|
utils.setAltitude(R.id.elevation_register, l.getAltitude());
|
||||||
utils.setUnknown(R.id.elevation_register);
|
utils.setLatLong(R.id.latitude_register, l.getLatitude());
|
||||||
utils.setUnknown(R.id.latitude_register);
|
utils.setLatLong(R.id.longitude_register, l.getLongitude());
|
||||||
utils.setUnknown(R.id.longitude_register);
|
utils.setSpeed(R.id.speed_register, l.getSpeed() * 3.6);
|
||||||
utils.setUnknown(R.id.speed_register);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ import com.google.android.apps.mytracks.util.StringUtils;
|
|||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Various utility functions for views that display statistics information.
|
* Various utility functions for views that display statistics information.
|
||||||
*
|
*
|
||||||
@@ -218,7 +218,7 @@ public class StatsUtilities {
|
|||||||
setGrade(R.id.max_grade_register, maxGrade);
|
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());
|
setTime(R.id.moving_time_register, stats.getMovingTime());
|
||||||
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
|
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
|
||||||
setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6);
|
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());
|
setAltitude(R.id.elevation_gain_register, stats.getTotalElevationGain());
|
||||||
setGrade(R.id.min_grade_register, stats.getMinGrade());
|
setGrade(R.id.min_grade_register, stats.getMinGrade());
|
||||||
setGrade(R.id.max_grade_register, stats.getMaxGrade());
|
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) {
|
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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to access data from the mytracks content provider.
|
* 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
|
* @return A cursor over the selected range of locations
|
||||||
*/
|
*/
|
||||||
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
int maxWaypoints);
|
long maxWaypoints);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a track by given unique track id.
|
* Finds a track by given unique track id.
|
||||||
@@ -187,15 +184,6 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
Track getTrack(long id);
|
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.
|
* Loads the track points for a given track.
|
||||||
*
|
*
|
||||||
@@ -206,6 +194,17 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
long getTrackPoints(Track track, int maxPoints);
|
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.
|
* Creates a cursor over the tracks provider with a given selection.
|
||||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||||
@@ -268,24 +267,6 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
void updateTrack(Track track);
|
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.
|
* Creates a location object from a given cursor.
|
||||||
*
|
*
|
||||||
@@ -294,14 +275,6 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
Location createLocation(Cursor cursor);
|
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.
|
* Creates a waypoint object from a given cursor.
|
||||||
*
|
*
|
||||||
@@ -345,7 +318,7 @@ public interface MyTracksProviderUtils {
|
|||||||
* Creates an instance of {@link MyTracksProviderUtils}.
|
* Creates an instance of {@link MyTracksProviderUtils}.
|
||||||
*/
|
*/
|
||||||
protected MyTracksProviderUtils newForContext(Context context) {
|
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 com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class providing easy access to locations and tracks in the
|
* Helper class providing easy access to locations and tracks in the
|
||||||
* MyTracksProvider. All static members.
|
* MyTracksProvider. All static members.
|
||||||
@@ -35,10 +32,10 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final Context context;
|
||||||
|
|
||||||
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
|
public MyTracksProviderUtilsImpl(Context context) {
|
||||||
this.contentResolver = contentResolver;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,8 +74,15 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return values;
|
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();
|
ContentValues values = new ContentValues();
|
||||||
TripStatistics stats = track.getStatistics();
|
TripStatistics stats = track.getStatistics();
|
||||||
|
|
||||||
@@ -171,15 +175,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Location createLocation(Cursor cursor) {
|
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 idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
|
||||||
int idxLongitude =
|
int idxLongitude =
|
||||||
cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
|
cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
|
||||||
@@ -189,6 +184,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
|
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
|
||||||
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
|
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
|
||||||
|
|
||||||
|
Location location = new Location("");
|
||||||
if (!cursor.isNull(idxLatitude)) {
|
if (!cursor.isNull(idxLatitude)) {
|
||||||
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
|
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
|
||||||
}
|
}
|
||||||
@@ -210,10 +206,16 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
if (!cursor.isNull(idxAccuracy)) {
|
if (!cursor.isNull(idxAccuracy)) {
|
||||||
location.setAccuracy(cursor.getFloat(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 idxId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||||
int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
|
||||||
int idxDescription =
|
int idxDescription =
|
||||||
@@ -448,22 +450,25 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteAllTracks() {
|
public void deleteAllTracks() {
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
|
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
null, null);
|
||||||
|
context.getContentResolver().delete(
|
||||||
|
WaypointsColumns.CONTENT_URI, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteTrack(long trackId) {
|
public void deleteTrack(long trackId) {
|
||||||
Track track = getTrack(trackId);
|
Track track = getTrack(trackId);
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI,
|
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||||
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
|
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI,
|
context.getContentResolver().delete(WaypointsColumns.CONTENT_URI,
|
||||||
WaypointsColumns.TRACKID + "=" + trackId, null);
|
WaypointsColumns.TRACKID + "=" + trackId, null);
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
context.getContentResolver().delete(
|
||||||
|
TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -488,7 +493,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
"No statistics marker after the deleted one was found.");
|
"No statistics marker after the deleted one was found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentResolver.delete(
|
context.getContentResolver().delete(
|
||||||
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
|
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +505,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
|
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
selection,
|
selection,
|
||||||
@@ -522,7 +527,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public boolean updateWaypoint(Waypoint waypoint) {
|
public boolean updateWaypoint(Waypoint waypoint) {
|
||||||
try {
|
try {
|
||||||
final int rows = contentResolver.update(
|
final int rows = context.getContentResolver().update(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
createContentValues(waypoint),
|
createContentValues(waypoint),
|
||||||
"_id=" + waypoint.getId(),
|
"_id=" + waypoint.getId(),
|
||||||
@@ -543,7 +548,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
private Location findLocationBy(String select) {
|
private Location findLocationBy(String select) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI, null, select, null, null);
|
TrackPointsColumns.CONTENT_URI, null, select, null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
return createLocation(cursor);
|
return createLocation(cursor);
|
||||||
@@ -567,7 +572,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
private Track findTrackBy(String select) {
|
private Track findTrackBy(String select) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, select, null, null);
|
TracksColumns.CONTENT_URI, null, select, null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
return createTrack(cursor);
|
return createTrack(cursor);
|
||||||
@@ -589,7 +594,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getFirstWaypoint(long trackId) {
|
public Waypoint getFirstWaypoint(long trackId) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
"trackid=" + trackId,
|
"trackid=" + trackId,
|
||||||
@@ -611,7 +616,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getWaypoint(long waypointId) {
|
public Waypoint getWaypoint(long waypointId) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
"_id=" + waypointId,
|
"_id=" + waypointId,
|
||||||
@@ -634,7 +639,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastLocationId(long trackId) {
|
public long getLastLocationId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI,
|
TrackPointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
|
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
|
||||||
@@ -658,7 +663,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getFirstWaypointId(long trackId) {
|
public long getFirstWaypointId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
"trackid=" + trackId,
|
"trackid=" + trackId,
|
||||||
@@ -682,7 +687,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastWaypointId(long trackId) {
|
public long getLastWaypointId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
WaypointsColumns.TRACKID + "=" + trackId,
|
WaypointsColumns.TRACKID + "=" + trackId,
|
||||||
@@ -707,7 +712,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
public Track getLastTrack() {
|
public Track getLastTrack() {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
|
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
|
||||||
null, null);
|
null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
@@ -726,7 +731,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastTrackId() {
|
public long getLastTrackId() {
|
||||||
String[] proj = { TracksColumns._ID };
|
String[] proj = { TracksColumns._ID };
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
|
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
|
||||||
null, null);
|
null, null);
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
@@ -766,13 +771,13 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
sortOrder += " LIMIT " + maxLocations;
|
sortOrder += " LIMIT " + maxLocations;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.query(
|
return context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
int maxWaypoints) {
|
long maxWaypoints) {
|
||||||
String selection;
|
String selection;
|
||||||
if (minWaypointId > 0) {
|
if (minWaypointId > 0) {
|
||||||
selection = String.format("%s=%d AND %s>=%d",
|
selection = String.format("%s=%d AND %s>=%d",
|
||||||
@@ -788,7 +793,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
sortOrder += " LIMIT " + maxWaypoints;
|
sortOrder += " LIMIT " + maxWaypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.query(
|
return context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,21 +803,6 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return findTrackBy(select);
|
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
|
@Override
|
||||||
public long getTrackPoints(Track track, int maxPoints) {
|
public long getTrackPoints(Track track, int maxPoints) {
|
||||||
long lastId = -1;
|
long lastId = -1;
|
||||||
@@ -842,9 +832,56 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return lastId;
|
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
|
@Override
|
||||||
public Cursor getTracksCursor(String selection) {
|
public Cursor getTracksCursor(String selection) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@@ -852,14 +889,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public Uri insertTrack(Track track) {
|
public Uri insertTrack(Track track) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
|
||||||
return contentResolver.insert(TracksColumns.CONTENT_URI,
|
return context.getContentResolver().insert(TracksColumns.CONTENT_URI,
|
||||||
createContentValues(track));
|
createContentValues(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insertTrackPoint(Location location, long trackId) {
|
public Uri insertTrackPoint(Location location, long trackId) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
|
||||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
|
return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI,
|
||||||
createContentValues(location, trackId));
|
createContentValues(location, trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,14 +909,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
values[i] = createContentValues(locations[i], trackId);
|
values[i] = createContentValues(locations[i], trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
return context.getContentResolver().bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insertWaypoint(Waypoint waypoint) {
|
public Uri insertWaypoint(Waypoint waypoint) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
|
||||||
waypoint.setId(-1);
|
waypoint.setId(-1);
|
||||||
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
|
return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI,
|
||||||
createContentValues(waypoint));
|
createContentValues(waypoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,7 +925,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
final String[] projection = { TracksColumns._ID };
|
final String[] projection = { TracksColumns._ID };
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI,
|
TracksColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
TracksColumns._ID + "=" + id/*selection*/,
|
TracksColumns._ID + "=" + id/*selection*/,
|
||||||
@@ -908,7 +945,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public void updateTrack(Track track) {
|
public void updateTrack(Track track) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
|
||||||
contentResolver.update(TracksColumns.CONTENT_URI,
|
context.getContentResolver().update(TracksColumns.CONTENT_URI,
|
||||||
createContentValues(track), "_id=" + track.getId(), null);
|
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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
@@ -44,14 +42,4 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
public static final String TIME = "time";
|
public static final String TIME = "time";
|
||||||
public static final String ACCURACY = "accuracy";
|
public static final String ACCURACY = "accuracy";
|
||||||
public static final String SPEED = "speed";
|
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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
@@ -61,16 +59,4 @@ public interface TracksColumns extends BaseColumns {
|
|||||||
public static final String MINLON = "minlon";
|
public static final String MINLON = "minlon";
|
||||||
public static final String MAXLON = "maxlon";
|
public static final String MAXLON = "maxlon";
|
||||||
public static final String MAPID = "mapid";
|
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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
@@ -68,19 +66,4 @@ public interface WaypointsColumns extends BaseColumns {
|
|||||||
public static final String ELEVATIONGAIN = "elevationgain";
|
public static final String ELEVATIONGAIN = "elevationgain";
|
||||||
public static final String MINGRADE = "mingrade";
|
public static final String MINGRADE = "mingrade";
|
||||||
public static final String MAXGRADE = "maxgrade";
|
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 com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -46,7 +47,7 @@ import java.util.TimeZone;
|
|||||||
public class CsvTrackWriter implements TrackFormatWriter {
|
public class CsvTrackWriter implements TrackFormatWriter {
|
||||||
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
static {
|
{
|
||||||
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +56,13 @@ public class CsvTrackWriter implements TrackFormatWriter {
|
|||||||
private PrintWriter pw;
|
private PrintWriter pw;
|
||||||
private Track track;
|
private Track track;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultDirectory() {
|
||||||
|
return Environment.getExternalStorageDirectory()
|
||||||
|
+ System.getProperty("file.separator")
|
||||||
|
+ getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return TrackFileFormat.CSV.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.content.Track;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
@@ -29,8 +28,11 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.SimpleTimeZone;
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.parsers.SAXParser;
|
import javax.xml.parsers.SAXParser;
|
||||||
@@ -52,6 +54,17 @@ import org.xml.sax.helpers.DefaultHandler;
|
|||||||
*/
|
*/
|
||||||
public class GpxImporter extends 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.
|
* 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
|
* Reads GPS tracks from a GPX file and writes tracks and their coordinates to
|
||||||
* the database.
|
* the database.
|
||||||
*
|
*
|
||||||
|
* @param tracks a list of tracks
|
||||||
* @param is a input steam with gpx-xml data
|
* @param is a input steam with gpx-xml data
|
||||||
* @return long[] array of track ids written in the database
|
* @return long[] array of track ids written in the database
|
||||||
* @throws SAXException a parsing error
|
* @throws SAXException a parsing error
|
||||||
@@ -434,16 +448,7 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
private void onTimeElementEnd() throws SAXException {
|
private void onTimeElementEnd() throws SAXException {
|
||||||
if (location == null) { return; }
|
if (location == null) { return; }
|
||||||
|
|
||||||
// Parse the time
|
long time = parseTimeForAllFormats(content);
|
||||||
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
|
|
||||||
if (lastSegmentLocation != null) {
|
if (lastSegmentLocation != null) {
|
||||||
long timeDifference = time - lastSegmentLocation.getTime();
|
long timeDifference = time - lastSegmentLocation.getTime();
|
||||||
|
|
||||||
@@ -463,12 +468,11 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
location.setBearing(lastSegmentLocation.bearingTo(location));
|
location.setBearing(lastSegmentLocation.bearingTo(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the time
|
|
||||||
location.setTime(time);
|
location.setTime(time);
|
||||||
|
|
||||||
// initialize start time with time of first track point
|
// initialize start time with time of first track point
|
||||||
if (statsBuilder == null) {
|
if (statsBuilder == null) {
|
||||||
statsBuilder = new TripStatisticsBuilder(time);
|
statsBuilder = new TripStatisticsBuilder();
|
||||||
|
statsBuilder.resumeAt(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +506,46 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
return result;
|
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.
|
* 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 com.google.android.apps.mytracks.util.StringUtils;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -37,7 +38,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
|||||||
|
|
||||||
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
static {
|
{
|
||||||
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,13 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
|||||||
this.pw = new PrintWriter(out);
|
this.pw = new PrintWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultDirectory() {
|
||||||
|
return Environment.getExternalStorageDirectory()
|
||||||
|
+ System.getProperty("file.separator")
|
||||||
|
+ getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return TrackFileFormat.GPX.getExtension();
|
return TrackFileFormat.GPX.getExtension();
|
||||||
@@ -137,7 +145,6 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
|||||||
@Override
|
@Override
|
||||||
public void writeWaypoint(Waypoint waypoint) {
|
public void writeWaypoint(Waypoint waypoint) {
|
||||||
if (pw != null) {
|
if (pw != null) {
|
||||||
// TODO: The gpx spec says waypoints should come *before* tracks
|
|
||||||
Location l = waypoint.getLocation();
|
Location l = waypoint.getLocation();
|
||||||
if (l != null) {
|
if (l != null) {
|
||||||
pw.println("<wpt lat=\"" + l.getLatitude() + "\" lon=\""
|
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.content.Context;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -57,6 +58,12 @@ public class KmlTrackWriter implements TrackFormatWriter {
|
|||||||
this.pw = new PrintWriter(out);
|
this.pw = new PrintWriter(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultDirectory() {
|
||||||
|
return Environment.getExternalStorageDirectory()
|
||||||
|
+ System.getProperty("file.separator") + getExtension();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return TrackFileFormat.KML.getExtension();
|
return TrackFileFormat.KML.getExtension();
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class SendToDocs {
|
|||||||
androidClient = GDataClientFactory.getGDataClient(activity);
|
androidClient = GDataClientFactory.getGDataClient(activity);
|
||||||
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
||||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||||
wiseWrapper.setClient(gdataClient);
|
wiseWrapper.setClient(androidClient, gdataClient);
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"GData connection prepared: " + this.writelyAuth);
|
"GData connection prepared: " + this.writelyAuth);
|
||||||
String sheetTitle = "My Tracks";
|
String sheetTitle = "My Tracks";
|
||||||
@@ -220,7 +220,7 @@ public class SendToDocs {
|
|||||||
|
|
||||||
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
||||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||||
writelyWrapper.setClient(docsGdataClient);
|
writelyWrapper.setClient(androidClient, docsGdataClient);
|
||||||
|
|
||||||
// First try to find the spreadsheet:
|
// First try to find the spreadsheet:
|
||||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.google.android.apps.mytracks.MyTracksSettings;
|
|||||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
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.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
@@ -43,392 +44,53 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class used to transmit tracks to Google MyMaps.
|
* A helper class used to transmit tracks to Google MyMaps.
|
||||||
* A new instance should be used for each upload.
|
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class SendToMyMaps implements Runnable {
|
public class SendToMyMaps {
|
||||||
|
|
||||||
public static final String NEW_MAP_ID = "new";
|
|
||||||
private static final String END_ICON_URL =
|
private static final String END_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||||
private static final String START_ICON_URL =
|
private static final String START_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
|
||||||
|
|
||||||
private final Activity context;
|
private final Activity context;
|
||||||
private final AuthManager auth;
|
private final AuthManager auth;
|
||||||
private final long trackId;
|
private final long trackId;
|
||||||
private final ProgressIndicator progressIndicator;
|
private final ProgressIndicator progressIndicator;
|
||||||
private final OnSendCompletedListener onCompletion;
|
|
||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
private String mapId;
|
private String mapId;
|
||||||
|
|
||||||
// GData service
|
private String statusMessage = "";
|
||||||
|
private boolean success = true;
|
||||||
|
private Runnable onCompletion;
|
||||||
|
|
||||||
|
private HandlerThread handlerThread;
|
||||||
|
private Handler handler;
|
||||||
|
|
||||||
|
private String originalDescription;
|
||||||
|
|
||||||
private MyMapsGDataWrapper wrapper;
|
private MyMapsGDataWrapper wrapper;
|
||||||
private MyMapsGDataConverter gdataConverter;
|
private MyMapsGDataConverter gdataConverter;
|
||||||
|
|
||||||
// Progress status
|
private Location last = null;
|
||||||
private int totalLocationsRead;
|
|
||||||
private int totalLocationsPrepared;
|
|
||||||
private int totalLocationsUploaded;
|
|
||||||
private int totalLocations;
|
|
||||||
private int totalSegmentsUploaded;
|
|
||||||
|
|
||||||
public interface OnSendCompletedListener {
|
private Vector<Double> distances;
|
||||||
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
private Vector<Double> elevations;
|
||||||
}
|
private DoubleBuffer elevationBuffer;
|
||||||
|
|
||||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
|
||||||
long trackId, ProgressIndicator progressIndicator,
|
|
||||||
OnSendCompletedListener onCompletion) {
|
|
||||||
this.context = context;
|
|
||||||
this.mapId = mapId;
|
|
||||||
this.auth = auth;
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.progressIndicator = progressIndicator;
|
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
this.stringUtils = new StringUtils(context);
|
|
||||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Sending to MyMaps: trackId = " + trackId);
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
int statusMessageId = R.string.error_sending_to_mymap;
|
|
||||||
boolean success = true;
|
|
||||||
try {
|
|
||||||
gdataConverter = new MyMapsGDataConverter();
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue(1);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_reading_track);
|
|
||||||
|
|
||||||
// Get the track meta-data
|
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
String originalDescription = track.getDescription();
|
|
||||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
|
||||||
wrapper = new MyMapsGDataWrapper(context);
|
|
||||||
wrapper.setAuthManager(auth);
|
|
||||||
wrapper.setRetryOnAuthFailure(true);
|
|
||||||
|
|
||||||
// Create a new map if necessary:
|
|
||||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
|
||||||
if (isNewMap) {
|
|
||||||
success = createNewMap(track, originalDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload all of the segments of the track plus start/end markers
|
|
||||||
if (success) {
|
|
||||||
success = uploadAllTrackPoints(track, originalDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put waypoints.
|
|
||||||
if (success) {
|
|
||||||
success = uploadWaypoints(track);
|
|
||||||
if (!success) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: upload waypoints failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
statusMessageId = isNewMap
|
|
||||||
? R.string.status_new_mymap_has_been_created
|
|
||||||
: R.string.status_tracks_have_been_uploaded;
|
|
||||||
}
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
|
||||||
progressIndicator.setProgressValue(100);
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
|
||||||
} finally {
|
|
||||||
if (wrapper != null) {
|
|
||||||
wrapper.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean finalSuccess = success;
|
|
||||||
final int finalStatusMessageId = statusMessageId;
|
|
||||||
context.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
onCompletion.onSendCompleted(
|
|
||||||
mapId, finalSuccess, finalStatusMessageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map for the given track.
|
|
||||||
*
|
|
||||||
* @param track The track that will be uploaded to this map
|
|
||||||
* @return True on success.
|
|
||||||
*/
|
|
||||||
private boolean createNewMap(final Track track, final String description) {
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_creating_map);
|
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client) throws IOException, Exception {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
|
||||||
String mapFeed = MapsClient.getMapsFeed();
|
|
||||||
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
|
||||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
|
||||||
metaData.setTitle(track.getName());
|
|
||||||
metaData.setDescription(description + " - "
|
|
||||||
+ track.getCategory() + " - "
|
|
||||||
+ context.getString(R.string.new_map_description));
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean mapPublic = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
mapPublic = preferences.getBoolean(
|
|
||||||
context.getString(R.string.default_map_public_key), true);
|
|
||||||
}
|
|
||||||
metaData.setSearchable(mapPublic);
|
|
||||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
|
||||||
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
|
||||||
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
|
||||||
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
|
||||||
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadAllTrackPoints(
|
|
||||||
final Track track, String originalDescription) {
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean metricUnits = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
metricUnits =
|
|
||||||
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor locationsCursor =
|
|
||||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
|
||||||
try {
|
|
||||||
if (!locationsCursor.moveToFirst()) {
|
|
||||||
Log.w(MyTracksConstants.TAG, "Unable to get any points to upload");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead = 0;
|
|
||||||
totalLocationsPrepared = 0;
|
|
||||||
totalLocationsUploaded = 0;
|
|
||||||
totalLocations = locationsCursor.getCount();
|
|
||||||
totalSegmentsUploaded = 0;
|
|
||||||
|
|
||||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
|
||||||
int elevationSamplingFrequency =
|
|
||||||
Math.max(1, (int) (totalLocations / 250.0));
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
|
||||||
+ " on " + totalLocations);
|
|
||||||
double totalDistance = 0;
|
|
||||||
|
|
||||||
Vector<Double> distances = new Vector<Double>();
|
|
||||||
Vector<Double> elevations = new Vector<Double>();
|
|
||||||
DoubleBuffer elevationBuffer =
|
|
||||||
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
|
||||||
|
|
||||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_reading_track);
|
|
||||||
Location lastLocation = null;
|
|
||||||
do {
|
|
||||||
if (totalLocationsRead % 100 == 0) {
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
Location loc = providerUtils.createLocation(locationsCursor);
|
|
||||||
locations.add(loc);
|
|
||||||
|
|
||||||
if (totalLocationsRead == 0) {
|
|
||||||
// Put a marker at the first point of the first valid segment:
|
|
||||||
uploadMarker(track, loc, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the elevation profile.
|
|
||||||
if (loc != null && MyTracksUtils.isValidLocation(loc)) {
|
|
||||||
// All points go into the smoothing buffer...
|
|
||||||
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
|
|
||||||
: loc.getAltitude() * UnitConversions.M_TO_FT);
|
|
||||||
if (lastLocation != null) {
|
|
||||||
double dist = lastLocation.distanceTo(loc);
|
|
||||||
totalDistance += dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...but only a few points are really used to keep the url short.
|
|
||||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
|
||||||
distances.add(totalDistance);
|
|
||||||
elevations.add(elevationBuffer.getAverage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the location was not valid, it's a segment split, so make sure the
|
|
||||||
// distance between the previous segment and the new one is not accounted
|
|
||||||
// for in the next iteration.
|
|
||||||
lastLocation = loc;
|
|
||||||
|
|
||||||
// Every now and then, upload the accumulated points
|
|
||||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
|
||||||
== MAX_POINTS_PER_UPLOAD - 1) {
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead++;
|
|
||||||
} while (locationsCursor.moveToNext());
|
|
||||||
|
|
||||||
// Do a final upload with what's left
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put an end marker at the last point of the last valid segment:
|
|
||||||
if (lastLocation != null) {
|
|
||||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(
|
|
||||||
track, distances, elevations)
|
|
||||||
+ "</p>");
|
|
||||||
return uploadMarker(track, lastLocation, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
locationsCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadMarker(final Track track,
|
|
||||||
final Location location,
|
|
||||||
final boolean isStart) {
|
|
||||||
boolean okay = wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client)
|
|
||||||
throws AuthenticationException, IOException, Exception {
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
insertMarker(context, client, featureFeed, track, location, isStart);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return okay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
|
||||||
* 2nd failure.
|
|
||||||
*/
|
|
||||||
private void insertMarker(Context context, MapsClient client,
|
|
||||||
String featureFeed, Track track, Location loc,
|
|
||||||
boolean isStart) throws IOException, Exception {
|
|
||||||
Entry entry = gdataConverter.getEntryForFeature(
|
|
||||||
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
|
||||||
+ entry.getTitle());
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
|
||||||
// Retry once (often IOException is thrown on a timeout):
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a placemark MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @param isStart true if it's the start of the track, or false for end
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
|
||||||
Track track, Location loc, boolean isStart) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
|
||||||
if (isStart) {
|
|
||||||
myMapsFeature.setIconUrl(START_ICON_URL);
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setIconUrl(END_ICON_URL);
|
|
||||||
}
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
String name = track.getName() + " "
|
|
||||||
+ (isStart ? context.getString(R.string.start)
|
|
||||||
: context.getString(R.string.end));
|
|
||||||
myMapsFeature.setTitle(name);
|
|
||||||
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_preparing_track);
|
|
||||||
updateProgress();
|
|
||||||
|
|
||||||
int numLocations = locations.size();
|
|
||||||
if (numLocations < 2) {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Not preparing/uploading too few points");
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare/pre-process the points
|
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
|
||||||
|
|
||||||
// Start uploading them
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_sending_mymaps);
|
|
||||||
for (Track splitTrack : splitTracks) {
|
|
||||||
if (totalSegmentsUploaded > 1) {
|
|
||||||
splitTrack.setName(splitTrack.getName() + " "
|
|
||||||
+ String.format(
|
|
||||||
context.getString(R.string.part), totalSegmentsUploaded));
|
|
||||||
}
|
|
||||||
totalSegmentsUploaded++;
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Prepared feature for upload w/ "
|
|
||||||
+ splitTrack.getLocations().size() + " points.");
|
|
||||||
|
|
||||||
// Transmit tracks via GData feed:
|
|
||||||
// -------------------------------
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
|
||||||
if (!uploadTrackPoints(splitTrack)) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Uploading failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locations.clear();
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
updateProgress();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a buffer of locations for transmission to google maps.
|
* Prepares a buffer of locations for transmission to google maps.
|
||||||
@@ -438,8 +100,8 @@ public class SendToMyMaps implements Runnable {
|
|||||||
* @return an array of tracks each with a sub section of the points in the
|
* @return an array of tracks each with a sub section of the points in the
|
||||||
* original buffer
|
* original buffer
|
||||||
*/
|
*/
|
||||||
private ArrayList<Track> prepareLocations(
|
private ArrayList<Track> prepareLocations(Track track,
|
||||||
Track track, Iterable<Location> locations) {
|
TrackBuffer buffer) {
|
||||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
// Create segments from each full track:
|
// Create segments from each full track:
|
||||||
@@ -453,10 +115,8 @@ public class SendToMyMaps implements Runnable {
|
|||||||
segmentStats.setStartTime(trackStats.getStartTime());
|
segmentStats.setStartTime(trackStats.getStartTime());
|
||||||
segmentStats.setStopTime(trackStats.getStopTime());
|
segmentStats.setStopTime(trackStats.getStopTime());
|
||||||
boolean startNewTrackSegment = false;
|
boolean startNewTrackSegment = false;
|
||||||
for (Location loc : locations) {
|
for (int i = 0; i < buffer.getLocationsLoaded(); ++i) {
|
||||||
if (totalLocationsPrepared % 100 == 0) {
|
Location loc = buffer.get(i);
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
if (loc.getLatitude() > 90) {
|
if (loc.getLatitude() > 90) {
|
||||||
startNewTrackSegment = true;
|
startNewTrackSegment = true;
|
||||||
}
|
}
|
||||||
@@ -481,7 +141,6 @@ public class SendToMyMaps implements Runnable {
|
|||||||
segmentStats.setStartTime(loc.getTime());
|
segmentStats.setStartTime(loc.getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
totalLocationsPrepared++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
prepareTrackSegment(segment, splitTracks);
|
||||||
@@ -524,29 +183,236 @@ public class SendToMyMaps implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
||||||
|
* 2nd failure.
|
||||||
*/
|
*/
|
||||||
private boolean uploadTrackPoints(final Track track) {
|
private void insertMarker(Context context, MapsClient client,
|
||||||
|
String featureFeed, Track track, Location loc,
|
||||||
|
boolean isStart) throws IOException, Exception {
|
||||||
|
Entry entry = gdataConverter.getEntryForFeature(
|
||||||
|
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||||
|
+ entry.getTitle());
|
||||||
|
try {
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||||
|
// Retry once (often IOException is thrown on a timeout):
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry success on 2nd try!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadMarker(final Track track,
|
||||||
|
final Location location,
|
||||||
|
final boolean isStart) {
|
||||||
|
boolean okay = wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client)
|
||||||
|
throws AuthenticationException, IOException, Exception {
|
||||||
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
insertMarker(context, client, featureFeed, track, location, isStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current upload progress.
|
||||||
|
*
|
||||||
|
* @param track The track being uploaded
|
||||||
|
* @param totalLocationsRead The total number of locations already read and
|
||||||
|
* uploaded
|
||||||
|
* @param segmentSize The percent of total that each segment corresponds to
|
||||||
|
* @param progressInSegment The approximate progress in this segment
|
||||||
|
*/
|
||||||
|
private void setProgress(final Track track, int totalLocationsRead,
|
||||||
|
double segmentSize, double progressInSegment) {
|
||||||
|
long totalLocations = track.getStopId() - track.getStartId();
|
||||||
|
|
||||||
|
// The percent of the total that represents the completed part of this
|
||||||
|
// segment.
|
||||||
|
double percentSegmentInTotal = (segmentSize * progressInSegment);
|
||||||
|
|
||||||
|
int progress = (int) ((100.0 * totalLocationsRead / totalLocations)
|
||||||
|
+ percentSegmentInTotal);
|
||||||
|
progress = Math.min(99, progress);
|
||||||
|
progressIndicator.setProgressValue(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadAllTrackPoints(final Track track) {
|
||||||
|
int totalLocationsRead = 0;
|
||||||
|
// TODO: This might not actually be accurate.
|
||||||
|
long totalLocations = track.getStopId() - track.getStartId();
|
||||||
|
|
||||||
|
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||||
|
int elevationSamplingFrequency =
|
||||||
|
Math.max(1, (int) (totalLocations / 250.0));
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||||
|
+ " on " + totalLocations);
|
||||||
|
double totalDistance = 0;
|
||||||
|
Location lastLocation = null;
|
||||||
|
|
||||||
|
TrackBuffer buffer = new TrackBuffer((int) Math.min(2048, totalLocations));
|
||||||
|
int part = 1;
|
||||||
|
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean metricUnits = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
metricUnits =
|
||||||
|
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the progress monitor:
|
||||||
|
// For each track segment 33% = read, 33% = prep & 33% = upload
|
||||||
|
// Note: These numbers are very rough.
|
||||||
|
|
||||||
|
// The size of each segment in the 100% progress
|
||||||
|
double segmentSize = buffer.getSize() * 100.0 / totalLocations;
|
||||||
|
while (success && buffer.getLastLocationRead() < track.getStopId()) {
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.0);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_reading_track));
|
||||||
|
|
||||||
|
providerUtils.getTrackPoints(track, buffer);
|
||||||
|
if (buffer.getLocationsLoaded() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (totalLocationsRead == 0) {
|
||||||
|
// Put a marker at the first point of the first valid segment:
|
||||||
|
uploadMarker(track, buffer.get(0), true);
|
||||||
|
}
|
||||||
|
last = buffer.get(buffer.getLocationsLoaded() - 1);
|
||||||
|
|
||||||
|
// Extract the elevation profile.
|
||||||
|
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
||||||
|
// Add all points to the smoothing buffer.
|
||||||
|
Location l = buffer.get(i);
|
||||||
|
if (l != null && MyTracksUtils.isValidLocation(l)) {
|
||||||
|
elevationBuffer.setNext(metricUnits ? l.getAltitude()
|
||||||
|
: l.getAltitude() * UnitConversions.M_TO_FT);
|
||||||
|
if (lastLocation != null) {
|
||||||
|
double dist = lastLocation.distanceTo(l);
|
||||||
|
totalDistance += dist;
|
||||||
|
}
|
||||||
|
lastLocation = l;
|
||||||
|
|
||||||
|
// Only add some data points to keep the url short.
|
||||||
|
if (i % elevationSamplingFrequency == 0) {
|
||||||
|
distances.add(totalDistance);
|
||||||
|
elevations.add(elevationBuffer.getAverage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.3333);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_preparing_track));
|
||||||
|
|
||||||
|
ArrayList<Track> splitTracks = prepareLocations(track, buffer);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Track t : splitTracks) {
|
||||||
|
if (part > 1) {
|
||||||
|
t.setName(t.getName() + " "
|
||||||
|
+ String.format(context.getString(R.string.part), part));
|
||||||
|
}
|
||||||
|
part++;
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Prepared feature for upload w/ "
|
||||||
|
+ t.getLocations().size() + " points.");
|
||||||
|
|
||||||
|
//
|
||||||
|
// Transmit tracks via GData feed:
|
||||||
|
// -------------------------------
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||||
|
if (!uploadTrackPoints(track, t,
|
||||||
|
totalLocationsRead, segmentSize)) {
|
||||||
|
success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
totalLocationsRead += buffer.getLocationsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
||||||
|
*
|
||||||
|
* @param track the track meta-data is used for the map name
|
||||||
|
*/
|
||||||
|
private boolean uploadTrackPoints(final Track track, final Track currentTrack,
|
||||||
|
int totalLocationsRead, double segmentSize) {
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.6666);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_sending_mymaps));
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
@Override
|
@Override
|
||||||
public void query(MapsClient client)
|
public void query(MapsClient client)
|
||||||
throws AuthenticationException, IOException, Exception {
|
throws AuthenticationException, IOException, Exception {
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
||||||
uploadTrackPoints(track, client, featureFeed);
|
uploadTrackPoints(track, currentTrack, client, featureFeed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean uploadTrackPoints(Track splitTrack,
|
/**
|
||||||
|
* Creates a new map for the given track.
|
||||||
|
*
|
||||||
|
* @param track The track that will be uploaded to this map
|
||||||
|
* @return True on success.
|
||||||
|
*/
|
||||||
|
private boolean createNewMap(final Track track) {
|
||||||
|
progressIndicator.setProgressMessage(context
|
||||||
|
.getString(R.string.progress_message_creating_map));
|
||||||
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client) throws IOException, Exception {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
||||||
|
String mapFeed = MapsClient.getMapsFeed();
|
||||||
|
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
||||||
|
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||||
|
metaData.setTitle(track.getName());
|
||||||
|
metaData.setDescription(originalDescription + " - "
|
||||||
|
+ track.getCategory() + " - "
|
||||||
|
+ context.getString(R.string.new_map_description));
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean mapPublic = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
mapPublic = preferences.getBoolean(
|
||||||
|
context.getString(R.string.default_map_public_key), true);
|
||||||
|
}
|
||||||
|
metaData.setSearchable(mapPublic);
|
||||||
|
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||||
|
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
||||||
|
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
||||||
|
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
||||||
|
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadTrackPoints(final Track track,
|
||||||
|
Track splitTrack,
|
||||||
MapsClient client,
|
MapsClient client,
|
||||||
String featureFeed)
|
String featureFeed)
|
||||||
throws IOException, Exception {
|
throws IOException, Exception {
|
||||||
Entry entry = null;
|
Entry entry = null;
|
||||||
int numLocations = splitTrack.getLocations().size();
|
if (splitTrack.getId() != track.getId()
|
||||||
if (numLocations < 2) {
|
|| splitTrack.getLocations().size() < 2) {
|
||||||
// Need at least two points for a polyline:
|
// Need at least two points for a polyline:
|
||||||
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,29 +435,6 @@ public class SendToMyMaps implements Runnable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
|
||||||
if (track.getName().length() < 1) {
|
|
||||||
// Features must have a name (otherwise GData upload may fail):
|
|
||||||
myMapsFeature.setTitle("-");
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setTitle(track.getName());
|
|
||||||
}
|
|
||||||
myMapsFeature.setColor(0x80FF0000);
|
|
||||||
for (Location loc : track.getLocations()) {
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
}
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads all of the waypoints associated with this track to map.
|
* Uploads all of the waypoints associated with this track to map.
|
||||||
*
|
*
|
||||||
@@ -651,6 +494,185 @@ public class SendToMyMaps implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||||
|
long trackId, ProgressIndicator progressIndicator) {
|
||||||
|
this.context = context;
|
||||||
|
this.mapId = mapId;
|
||||||
|
this.auth = auth;
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.progressIndicator = progressIndicator;
|
||||||
|
this.stringUtils = new StringUtils(context);
|
||||||
|
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||||
|
|
||||||
|
Log.d(MyTracksConstants.TAG, "Sending to MyMap: trackId = " + trackId);
|
||||||
|
handlerThread = new HandlerThread("SendToMyMaps");
|
||||||
|
handlerThread.start();
|
||||||
|
handler = new Handler(handlerThread.getLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doUpload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCompletion(Runnable onCompletion) {
|
||||||
|
this.onCompletion = onCompletion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapId() {
|
||||||
|
return mapId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatusMessage() {
|
||||||
|
return statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUpload() {
|
||||||
|
statusMessage = context.getString(R.string.error_sending_to_mymap);
|
||||||
|
success = true;
|
||||||
|
try {
|
||||||
|
gdataConverter = new MyMapsGDataConverter();
|
||||||
|
|
||||||
|
progressIndicator.setProgressValue(1);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_reading_track));
|
||||||
|
|
||||||
|
// Get the track meta-data
|
||||||
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
originalDescription = track.getDescription();
|
||||||
|
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||||
|
wrapper = new MyMapsGDataWrapper(context);
|
||||||
|
wrapper.setAuthManager(auth);
|
||||||
|
wrapper.setRetryOnAuthFailure(true);
|
||||||
|
|
||||||
|
distances = new Vector<Double>();
|
||||||
|
elevations = new Vector<Double>();
|
||||||
|
elevationBuffer =
|
||||||
|
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
|
|
||||||
|
// Create a new map if necessary:
|
||||||
|
if (mapId.equals("new")) {
|
||||||
|
if (!createNewMap(track)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all of the segments of the track.
|
||||||
|
if (success) {
|
||||||
|
if (!uploadAllTrackPoints(track)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start end end markers.
|
||||||
|
if (success) {
|
||||||
|
// Put an end marker at the last point of the last valid segment:
|
||||||
|
if (last != null) {
|
||||||
|
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(
|
||||||
|
track, distances, elevations)
|
||||||
|
+ "</p>");
|
||||||
|
if (!uploadMarker(track, last, false)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put waypoints.
|
||||||
|
if (success) {
|
||||||
|
if (!uploadWaypoints(track)) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: upload waypoints failed.");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (mapId.equals("new")) {
|
||||||
|
statusMessage =
|
||||||
|
context.getString(R.string.status_new_mymap_has_been_created);
|
||||||
|
} else {
|
||||||
|
statusMessage =
|
||||||
|
context.getString(R.string.status_tracks_have_been_uploaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
||||||
|
progressIndicator.setProgressValue(100);
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||||
|
} finally {
|
||||||
|
if (wrapper != null) {
|
||||||
|
wrapper.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (onCompletion != null) {
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||||
|
if (track.getName().length() < 1) {
|
||||||
|
// Features must have a name (otherwise GData upload may fail):
|
||||||
|
myMapsFeature.setTitle("-");
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setTitle(track.getName());
|
||||||
|
}
|
||||||
|
myMapsFeature.setColor(0x80FF0000);
|
||||||
|
for (Location loc : track.getLocations()) {
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
}
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a placemark MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param isStart true if it's the start of the track, or false for end
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
||||||
|
Track track, Location loc, boolean isStart) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||||
|
if (isStart) {
|
||||||
|
myMapsFeature.setIconUrl(START_ICON_URL);
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setIconUrl(END_ICON_URL);
|
||||||
|
}
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
String name = track.getName() + " "
|
||||||
|
+ (isStart ? context.getString(R.string.start)
|
||||||
|
: context.getString(R.string.end));
|
||||||
|
myMapsFeature.setTitle(name);
|
||||||
|
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a MyMapsFeature from a track.
|
* Builds a MyMapsFeature from a track.
|
||||||
*
|
*
|
||||||
@@ -673,17 +695,4 @@ public class SendToMyMaps implements Runnable {
|
|||||||
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
||||||
return myMapsFeature;
|
return myMapsFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current upload progress.
|
|
||||||
*/
|
|
||||||
private void updateProgress() {
|
|
||||||
// The percent of the total that represents the completed part of this
|
|
||||||
// segment.
|
|
||||||
int totalPercentage =
|
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
|
||||||
/ (totalLocations * 3);
|
|
||||||
totalPercentage = Math.min(99, totalPercentage);
|
|
||||||
progressIndicator.setProgressValue(totalPercentage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
|||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -42,7 +43,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
|
|
||||||
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
static final SimpleDateFormat TIMESTAMP_FORMAT =
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||||
static {
|
{
|
||||||
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
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
|
@Override
|
||||||
public String getExtension() {
|
public String getExtension() {
|
||||||
return TrackFileFormat.TCX.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);
|
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, ...)
|
* @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.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
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.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
@@ -27,12 +27,14 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class which exports tracks to the SD card.
|
* The class which exports tracks to the SD card.
|
||||||
@@ -49,20 +51,39 @@ public class TrackWriter {
|
|||||||
private final MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
private final Track track;
|
private final Track track;
|
||||||
private final TrackFormatWriter writer;
|
private final TrackFormatWriter writer;
|
||||||
private final FileUtils fileUtils;
|
|
||||||
private Runnable onCompletion = null;
|
private Runnable onCompletion = null;
|
||||||
private boolean success = false;
|
private boolean success = false;
|
||||||
private int errorMessage = -1;
|
private int errorMessage = -1;
|
||||||
private File directory = null;
|
private File directory = null;
|
||||||
private File file = 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,
|
public TrackWriter(Context context, MyTracksProviderUtils providerUtils,
|
||||||
Track track, TrackFormatWriter writer) {
|
Track track, TrackFormatWriter writer) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.providerUtils = providerUtils;
|
this.providerUtils = providerUtils;
|
||||||
this.track = track;
|
this.track = track;
|
||||||
this.writer = writer;
|
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() {
|
private void finished() {
|
||||||
if (onCompletion != null) {
|
if (onCompletion != null) {
|
||||||
runOnUiThread(onCompletion);
|
runOnUiThread(onCompletion);
|
||||||
@@ -154,15 +195,9 @@ public class TrackWriter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the file doesn't exist yet (possibly by changing the filename)
|
// Make sure the name will work on FAT
|
||||||
String fileName = fileUtils.buildUniqueFileName(
|
String fileName =
|
||||||
directory, track.getName(), writer.getExtension());
|
sanitizeName(track.getName()) + "." + writer.getExtension();
|
||||||
if (fileName == null) {
|
|
||||||
Log.e(MyTracksConstants.TAG,
|
|
||||||
"Unable to get a unique filename for " + fileName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(MyTracksConstants.TAG, "Writing track to: " + fileName);
|
Log.i(MyTracksConstants.TAG, "Writing track to: " + fileName);
|
||||||
try {
|
try {
|
||||||
writer.prepare(track, newOutputStream(fileName));
|
writer.prepare(track, newOutputStream(fileName));
|
||||||
@@ -179,18 +214,17 @@ public class TrackWriter {
|
|||||||
*/
|
*/
|
||||||
protected boolean canWriteFile() {
|
protected boolean canWriteFile() {
|
||||||
if (directory == null) {
|
if (directory == null) {
|
||||||
String dirName =
|
directory = newFile(writer.getDefaultDirectory());
|
||||||
fileUtils.buildExternalDirectoryPath(writer.getExtension());
|
|
||||||
directory = newFile(dirName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fileUtils.isSdCardAvailable()) {
|
if (!Environment.getExternalStorageState()
|
||||||
|
.equals(Environment.MEDIA_MOUNTED)) {
|
||||||
Log.i(MyTracksConstants.TAG, "Could not find SD card.");
|
Log.i(MyTracksConstants.TAG, "Could not find SD card.");
|
||||||
errorMessage = R.string.io_no_external_storage_found;
|
errorMessage = R.string.io_no_external_storage_found;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!fileUtils.ensureDirectoryExists(directory)) {
|
if (!ensureExists(directory)) {
|
||||||
Log.i(MyTracksConstants.TAG, "Could not create export directory.");
|
Log.i(MyTracksConstants.TAG, "Could not create GPX directory.");
|
||||||
errorMessage = R.string.io_create_dir_failed;
|
errorMessage = R.string.io_create_dir_failed;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -216,6 +250,25 @@ public class TrackWriter {
|
|||||||
return new File(path);
|
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.
|
* Writes the waypoints for the given track.
|
||||||
*
|
*
|
||||||
@@ -253,8 +306,55 @@ public class TrackWriter {
|
|||||||
void writeDocument() {
|
void writeDocument() {
|
||||||
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
||||||
writer.writeHeader();
|
writer.writeHeader();
|
||||||
// TODO: Fix ordering (in GPX waypoints should come first)
|
TrackBuffer buffer = new TrackBuffer(1024);
|
||||||
writeLocations();
|
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());
|
writeWaypoints(track.getId());
|
||||||
writer.writeFooter();
|
writer.writeFooter();
|
||||||
writer.close();
|
writer.close();
|
||||||
@@ -262,66 +362,4 @@ public class TrackWriter {
|
|||||||
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
||||||
errorMessage = R.string.io_write_finished;
|
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
|
* @param format the output format to write in
|
||||||
* @return the new track writer
|
* @return the new track writer
|
||||||
*/
|
*/
|
||||||
private static TrackWriter newWriter(Context context,
|
public static TrackWriter newWriter(Context context,
|
||||||
MyTracksProviderUtils providerUtils,
|
MyTracksProviderUtils providerUtils,
|
||||||
Track track, TrackFileFormat format) {
|
Track track, TrackFileFormat format) {
|
||||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
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.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.io.AuthManager;
|
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.GDataServiceClient;
|
||||||
import com.google.wireless.gdata.client.HttpException;
|
import com.google.wireless.gdata.client.HttpException;
|
||||||
import com.google.wireless.gdata.parser.ParseException;
|
import com.google.wireless.gdata.parser.ParseException;
|
||||||
@@ -27,10 +28,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,63 +84,43 @@ public class GDataWrapper {
|
|||||||
// An unknown error occurred.
|
// An unknown error occurred.
|
||||||
public static final int ERROR_UNKNOWN = 100;
|
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 String errorMessage;
|
||||||
private int errorType;
|
private int errorType;
|
||||||
|
private GDataClient androidGDataClient;
|
||||||
private GDataServiceClient gdataServiceClient;
|
private GDataServiceClient gdataServiceClient;
|
||||||
private AuthManager auth;
|
private AuthManager auth;
|
||||||
private boolean retryOnAuthFailure;
|
private boolean retryOnAuthFailure;
|
||||||
|
private int retriesPending;
|
||||||
|
private boolean cleanupCalled;
|
||||||
|
|
||||||
public GDataWrapper() {
|
public GDataWrapper() {
|
||||||
errorType = ERROR_NO_ERROR;
|
errorType = ERROR_NO_ERROR;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
auth = null;
|
auth = null;
|
||||||
retryOnAuthFailure = false;
|
retryOnAuthFailure = false;
|
||||||
|
retriesPending = 0;
|
||||||
|
cleanupCalled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClient(GDataServiceClient gdataServiceClient) {
|
public void setClient(GDataClient androidGDataClient,
|
||||||
|
GDataServiceClient gdataServiceClient) {
|
||||||
|
this.androidGDataClient = androidGDataClient;
|
||||||
this.gdataServiceClient = gdataServiceClient;
|
this.gdataServiceClient = gdataServiceClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean runAuthenticatedFunction(
|
public boolean runAuthenticatedFunction(
|
||||||
final AuthenticatedFunction function) {
|
final AuthenticatedFunction function) {
|
||||||
return runCommon(function, null);
|
return runOne(function, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean runQuery(final QueryFunction query) {
|
public boolean runQuery(final QueryFunction query) {
|
||||||
return runCommon(null, query);
|
return runOne(null, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an arbitrary piece of code.
|
* Runs an arbitrary piece of code.
|
||||||
*/
|
*/
|
||||||
private boolean runCommon(final AuthenticatedFunction function,
|
private boolean runOne(final AuthenticatedFunction function,
|
||||||
final QueryFunction query) {
|
|
||||||
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
|
|
||||||
runOne(function, query);
|
|
||||||
if (errorType == ERROR_NO_ERROR) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
|
||||||
if (errorType == ERROR_AUTH && auth != null) {
|
|
||||||
if (!retryOnAuthFailure || !invalidateAndRefreshAuthToken()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG, "retrying function/query");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a given function or query. If one is executed, errorType and
|
|
||||||
* errorMessage will contain the result/status of the function/query.
|
|
||||||
*/
|
|
||||||
private void runOne(final AuthenticatedFunction function,
|
|
||||||
final QueryFunction query) {
|
final QueryFunction query) {
|
||||||
try {
|
try {
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
@@ -151,14 +128,11 @@ public class GDataWrapper {
|
|||||||
} else if (query != null) {
|
} else if (query != null) {
|
||||||
query.query(gdataServiceClient);
|
query.query(gdataServiceClient);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
return false;
|
||||||
"invalid invocation of runOne; one of function/query " +
|
|
||||||
"must be non-null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorType = ERROR_NO_ERROR;
|
errorType = ERROR_NO_ERROR;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||||
errorType = ERROR_AUTH;
|
errorType = ERROR_AUTH;
|
||||||
@@ -192,42 +166,32 @@ public class GDataWrapper {
|
|||||||
errorType = ERROR_CONFLICT;
|
errorType = ERROR_CONFLICT;
|
||||||
errorMessage = e.getMessage();
|
errorMessage = e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
||||||
* Invalidates and refreshes the auth token. Blocks until the refresh has
|
if (errorType == ERROR_AUTH && auth != null) {
|
||||||
* completed or until we deem the refresh as having timed out.
|
Runnable whenFinished = null;
|
||||||
*
|
if (retryOnAuthFailure) {
|
||||||
* @return true If the invalidate/refresh succeeds, false if it fails or
|
retriesPending++;
|
||||||
* times out.
|
// This is a little odd. This method cannot return in the the thread
|
||||||
*/
|
// that called the original runQuery(). We should look at reworking this
|
||||||
private boolean invalidateAndRefreshAuthToken() {
|
// API.
|
||||||
Log.d(MyTracksConstants.TAG, "Retrying due to auth failure");
|
whenFinished = new Runnable() {
|
||||||
// This FutureTask doesn't do anything -- it exists simply to be
|
public void run() {
|
||||||
// blocked upon using get().
|
retriesPending--;
|
||||||
FutureTask<?> whenFinishedFuture = new FutureTask<Object>(new Runnable() {
|
try {
|
||||||
public void run() {}
|
runOne(function, query);
|
||||||
}, null);
|
} catch (IllegalStateException ise) {
|
||||||
|
// This can happen if the connection pool was shut down.
|
||||||
auth.invalidateAndRefresh(whenFinishedFuture);
|
Log.e(MyTracksConstants.TAG, "Failed to rerun query.", ise);
|
||||||
|
}
|
||||||
try {
|
if (cleanupCalled && retriesPending == 0) {
|
||||||
Log.d(MyTracksConstants.TAG, "waiting for invalidate");
|
cleanUp();
|
||||||
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
|
}
|
||||||
TimeUnit.MILLISECONDS);
|
}
|
||||||
Log.d(MyTracksConstants.TAG, "invalidate finished");
|
};
|
||||||
return true;
|
}
|
||||||
|
auth.invalidateAndRefresh(whenFinished);
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Invalidate didn't complete in time", e);
|
|
||||||
} finally {
|
|
||||||
whenFinishedFuture.cancel(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +203,17 @@ public class GDataWrapper {
|
|||||||
return errorMessage;
|
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) {
|
public void setAuthManager(AuthManager auth) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
* Deletes all the stored tracks.
|
||||||
*/
|
*/
|
||||||
void deleteAllTracks();
|
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;
|
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
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class PeriodicTaskExecuter {
|
public class PeriodicTaskExecuter {
|
||||||
|
|
||||||
private final PeriodicTask task;
|
|
||||||
private final TrackRecordingService service;
|
private final TrackRecordingService service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A timer to schedule the announcements.
|
* 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,
|
public PeriodicTaskExecuter(PeriodicTask task,
|
||||||
TrackRecordingService service) {
|
TrackRecordingService service) {
|
||||||
@@ -52,19 +52,8 @@ public class PeriodicTaskExecuter {
|
|||||||
* @param interval The interval in milliseconds
|
* @param interval The interval in milliseconds
|
||||||
*/
|
*/
|
||||||
public void scheduleTask(long interval) {
|
public void scheduleTask(long interval) {
|
||||||
// TODO: Decouple service from this class once and forever.
|
timer.cancel();
|
||||||
if (!service.isRecording()) {
|
timer.purge();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer != null) {
|
|
||||||
timer.cancel();
|
|
||||||
timer.purge();
|
|
||||||
} else {
|
|
||||||
// First start, or we were previously shut down.
|
|
||||||
task.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
timer = new Timer();
|
timer = new Timer();
|
||||||
if (interval <= 0) {
|
if (interval <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -72,14 +61,12 @@ public class PeriodicTaskExecuter {
|
|||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long next = service.getTripStatistics().getStartTime();
|
long next = service.getTripStatistics().getStartTime();
|
||||||
if (next < now) {
|
while (next < now) next += interval;
|
||||||
next = now + interval - ((now - next) % interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
Date start = new Date(next);
|
Date start = new Date(next);
|
||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
task.getClass().getSimpleName() + " scheduled to start at " + start
|
"StatusAnnouncer scheduled to start at " + start + " every "
|
||||||
+ " every " + interval + " milliseconds.");
|
+ interval + " milliseconds.");
|
||||||
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,14 +74,10 @@ public class PeriodicTaskExecuter {
|
|||||||
* Cleans up this object.
|
* Cleans up this object.
|
||||||
*/
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
Log.i(MyTracksConstants.TAG,
|
timer.cancel();
|
||||||
task.getClass().getSimpleName() + " shutting down.");
|
timer.purge();
|
||||||
if (timer != null) {
|
timer = null;
|
||||||
timer.cancel();
|
task.shutdown();
|
||||||
timer.purge();
|
|
||||||
timer = null;
|
|
||||||
task.shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,70 +15,47 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.services;
|
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.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||||
import com.google.android.maps.mytracks.R;
|
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.
|
* A class that manages reading the shared preferences for the service.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
public class PreferenceManager {
|
||||||
private TrackRecordingService service;
|
private TrackRecordingService service;
|
||||||
private SharedPreferences sharedPreferences;
|
private String announcement_frequency_key;
|
||||||
private final String announcementFrequencyKey;
|
private String max_recording_distance_key;
|
||||||
private final String autoResumeTrackCurrentRetryKey;
|
private String metric_units_key;
|
||||||
private final String autoResumeTrackTimeoutKey;
|
private String min_recording_distance_key;
|
||||||
private final String maxRecordingDistanceKey;
|
private String min_recording_interval_key;
|
||||||
private final String metricUnitsKey;
|
private String min_required_accuracy_key;
|
||||||
private final String minRecordingDistanceKey;
|
private String signal_sampling_frequency_key;
|
||||||
private final String minRecordingIntervalKey;
|
private String split_frequency_key;
|
||||||
private final String minRequiredAccuracyKey;
|
|
||||||
private final String recordingTrackKey;
|
|
||||||
private final String signalSamplingFrequencyKey;
|
|
||||||
private final String splitFrequencyKey;
|
|
||||||
|
|
||||||
public PreferenceManager(TrackRecordingService service) {
|
public PreferenceManager(TrackRecordingService service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.sharedPreferences = service.getSharedPreferences(
|
announcement_frequency_key =
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
service.getString(R.string.announcement_frequency_key);
|
||||||
if (sharedPreferences == null) {
|
max_recording_distance_key =
|
||||||
Log.w(MyTracksConstants.TAG,
|
service.getString(R.string.max_recording_distance_key);
|
||||||
"TrackRecordingService: Couldn't get shared preferences.");
|
metric_units_key =
|
||||||
throw new IllegalStateException("Couldn't get shared preferences");
|
service.getString(R.string.metric_units_key);
|
||||||
}
|
min_recording_distance_key =
|
||||||
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 =
|
|
||||||
service.getString(R.string.min_recording_distance_key);
|
service.getString(R.string.min_recording_distance_key);
|
||||||
minRecordingIntervalKey =
|
min_recording_interval_key =
|
||||||
service.getString(R.string.min_recording_interval_key);
|
service.getString(R.string.min_recording_interval_key);
|
||||||
minRequiredAccuracyKey =
|
min_required_accuracy_key =
|
||||||
service.getString(R.string.min_required_accuracy_key);
|
service.getString(R.string.min_required_accuracy_key);
|
||||||
recordingTrackKey =
|
split_frequency_key =
|
||||||
service.getString(R.string.recording_track_key);
|
service.getString(R.string.split_frequency_key);
|
||||||
signalSamplingFrequencyKey =
|
signal_sampling_frequency_key =
|
||||||
service.getString(R.string.signal_sampling_frequency_key);
|
service.getString(R.string.signal_sampling_frequency_key);
|
||||||
splitFrequencyKey =
|
|
||||||
service.getString(R.string.split_frequency_key);
|
|
||||||
|
|
||||||
// Refresh all properties.
|
|
||||||
onSharedPreferenceChanged(sharedPreferences, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,32 +64,33 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
*
|
*
|
||||||
* @param key the key that changed (may be null to update all preferences)
|
* @param key the key that changed (may be null to update all preferences)
|
||||||
*/
|
*/
|
||||||
@Override
|
public void onSharedPreferenceChanged(String key) {
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
SharedPreferences sharedPreferences =
|
||||||
String key) {
|
service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
if (service == null) {
|
if (sharedPreferences == null) {
|
||||||
Log.w(MyTracksConstants.TAG,
|
Log.w(MyTracksConstants.TAG,
|
||||||
"onSharedPreferenceChanged: a preference change (key = " + key
|
"TrackRecordingService: Couldn't get shared preferences.");
|
||||||
+ ") after a call to shutdown()");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(minRecordingDistanceKey)) {
|
|
||||||
|
if (key == null || key.equals(min_recording_distance_key)) {
|
||||||
service.setMinRecordingDistance(
|
service.setMinRecordingDistance(
|
||||||
sharedPreferences.getInt(
|
sharedPreferences.getInt(
|
||||||
minRecordingDistanceKey,
|
min_recording_distance_key,
|
||||||
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE));
|
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE));
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"TrackRecordingService: minRecordingDistance = "
|
"TrackRecordingService: minRecordingDistance = "
|
||||||
+ service.getMinRecordingDistance());
|
+ service.getMinRecordingDistance());
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(maxRecordingDistanceKey)) {
|
if (key == null || key.equals(max_recording_distance_key)) {
|
||||||
service.setMaxRecordingDistance(sharedPreferences.getInt(
|
service.setMaxRecordingDistance(
|
||||||
maxRecordingDistanceKey,
|
sharedPreferences.getInt(
|
||||||
MyTracksSettings.DEFAULT_MAX_RECORDING_DISTANCE));
|
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(
|
int minRecordingInterval = sharedPreferences.getInt(
|
||||||
minRecordingIntervalKey,
|
min_recording_interval_key,
|
||||||
MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL);
|
MyTracksSettings.DEFAULT_MIN_RECORDING_INTERVAL);
|
||||||
switch (minRecordingInterval) {
|
switch (minRecordingInterval) {
|
||||||
case -2:
|
case -2:
|
||||||
@@ -136,59 +114,29 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
new AbsoluteLocationListenerPolicy(minRecordingInterval * 1000));
|
new AbsoluteLocationListenerPolicy(minRecordingInterval * 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(minRequiredAccuracyKey)) {
|
if (key == null || key.equals(min_required_accuracy_key)) {
|
||||||
service.setMinRequiredAccuracy(sharedPreferences.getInt(
|
service.setMinRequiredAccuracy(
|
||||||
minRequiredAccuracyKey,
|
sharedPreferences.getInt(
|
||||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY));
|
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(
|
service.setAnnouncementFrequency(
|
||||||
sharedPreferences.getInt(announcementFrequencyKey, -1));
|
sharedPreferences.getInt(announcement_frequency_key,
|
||||||
|
-1));
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(autoResumeTrackTimeoutKey)) {
|
if (key == null || key.equals(split_frequency_key)) {
|
||||||
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)) {
|
|
||||||
service.getSplitManager().setSplitFrequency(
|
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(
|
service.getSignalManager().setFrequency(
|
||||||
sharedPreferences.getInt(signalSamplingFrequencyKey, -1), service);
|
sharedPreferences.getInt(
|
||||||
|
signal_sampling_frequency_key, -1), service);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(metricUnitsKey)) {
|
if (key == null || key.equals(metric_units_key)) {
|
||||||
service.getSplitManager().setMetricUnits(
|
service.getSplitManager().setMetricUnits(sharedPreferences.getBoolean(
|
||||||
sharedPreferences.getBoolean(metricUnitsKey, true));
|
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() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,6 @@ public class SignalStrengthTask extends PhoneStateListener
|
|||||||
context = c;
|
context = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
public void start() {
|
||||||
manager =
|
manager =
|
||||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
@@ -177,7 +176,6 @@ public class SignalStrengthTask extends PhoneStateListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (manager != null) {
|
if (manager != null) {
|
||||||
manager.listen(this, PhoneStateListener.LISTEN_NONE);
|
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.
|
* Calculates the next distance that a split should be inserted at.
|
||||||
*/
|
*/
|
||||||
public void calculateNextSplit() {
|
public void calculateNextSplit() {
|
||||||
// TODO: Decouple service from this class once and forever.
|
|
||||||
if (!service.isRecording()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (splitFrequency >= 0) {
|
if (splitFrequency >= 0) {
|
||||||
nextSplitDistance = Double.MAX_VALUE;
|
nextSplitDistance = Double.MAX_VALUE;
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"SplitManager: Distance splits disabled.");
|
"SplitManager: Distance splits dissabled.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,15 +120,7 @@ public class SplitManager {
|
|||||||
* @param splitFrequency The frequency in time or distance
|
* @param splitFrequency The frequency in time or distance
|
||||||
*/
|
*/
|
||||||
public void setSplitFrequency(int splitFrequency) {
|
public void setSplitFrequency(int splitFrequency) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"setSplitFrequency: splitFrequency = " + splitFrequency);
|
|
||||||
this.splitFrequency = splitFrequency;
|
this.splitFrequency = splitFrequency;
|
||||||
|
|
||||||
// TODO: Decouple service from this class once and forever.
|
|
||||||
if (!service.isRecording()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (splitFrequency < 1) {
|
if (splitFrequency < 1) {
|
||||||
if (splitExecuter != null) {
|
if (splitExecuter != null) {
|
||||||
splitExecuter.shutdown();
|
splitExecuter.shutdown();
|
||||||
|
|||||||