Compare commits

...

68 Commits

Author SHA1 Message Date
Rodrigo Damazio c7db7c0926 Releasing version 1.1.2 2011-02-24 20:22:46 -03:00
Rodrigo Damazio e107766a61 Updating Maps library to fix cupcake/donut issues. 2011-02-24 15:19:38 -03:00
Rodrigo Damazio 05a0b6c615 Fixing app and uploading on Cupcake/Donut. 2011-02-24 14:46:24 -03:00
Rodrigo Damazio 887a77f4a3 Merging Bart's changes 2011-02-23 19:39:11 -03:00
Rodrigo Damazio 97bde67b1d Merging to head 2011-02-23 19:37:59 -03:00
Matthew Simmons 33c3668175 Disable debugging 2011-02-23 15:38:27 -05:00
Matthew Simmons 0158fc3a7a merge 2011-02-23 15:39:24 -05:00
Sandor Dornbush 72bb2ddb08 Address comments. 2011-02-23 12:32:07 -08:00
Sandor Dornbush 01c1a37fa2 Address comments. 2011-02-23 09:50:31 -08:00
Sandor Dornbush 35aa51d6a9 Request audio focus. 2011-02-22 22:13:44 -08:00
Matthew Simmons fe9b27ecf2 fix method name 2011-02-23 00:07:29 -05:00
Matthew Simmons e03f120a00 Remove ANT entries from preferences if the device does not support ANT. 2011-02-23 00:03:39 -05:00
Sandor Dornbush 59344f540d Tabs->spaces. 2011-02-22 19:06:49 -08:00
Sandor Dornbush e829f1fc1b Hide the first stats waypoint. 2011-02-22 16:52:22 -08:00
Rodrigo Damazio d9904eb5f1 Bring My Maps back 2011-02-22 19:55:27 -03:00
Matthew Simmons 10cd26af5e Upgrade ANT support libraries
Rework AntSensorManager (and subclasses) to use asynchronous ANT interfaces
Encapsulate message byte arrays with per-message classes
Add tests
2011-02-21 19:49:43 -05:00
Sandor Dornbush c1f649fc83 Remove strict mode violation from onLocationChanged 2011-02-18 16:12:49 -08:00
Rodrigo Damazio a4865f860d Adding missing fusion tables permission. 2011-02-18 14:07:59 -02:00
Sandor Dornbush 14fce8edfe Merge. 2011-02-17 22:09:47 -08:00
Sandor Dornbush 6826e3567d Fix build breakage. 2011-02-17 22:01:46 -08:00
Sandor Dornbush 250e8625a7 Shorten some strings in csv. 2011-02-17 21:33:47 -08:00
Sandor Dornbush 58530d6f72 Catalan update. 2011-02-17 21:02:35 -08:00
Sandor Dornbush 823fb3fca7 Merge. 2011-02-17 20:13:51 -08:00
Sandor Dornbush 29302c0891 Try to fix moving time. 2011-02-17 07:55:19 -08:00
Matthew Simmons 03570ebd43 merge 2011-02-16 11:35:41 -05:00
Sandor Dornbush 2a4a0d38e3 Merge import changes. 2011-02-16 07:42:25 -08:00
David Piggott 9be9127ad1 Tiny style improvement in ImportAllTracks 2011-02-16 08:19:57 +00:00
David Piggott 66dd1c01f0 Improve style and external storage access methods of ImportAllTracks 2011-02-16 01:29:20 +00:00
Matthew Simmons d8dce7938d Format the Fusion Tables URL locale-insensitively 2011-02-15 07:20:58 -05:00
Rodrigo Damazio 0701f5885e Merging Jared's heads. 2011-02-14 15:01:41 -02:00
Rodrigo Damazio ea3f660999 Merging Jared's heads. 2011-02-14 14:54:31 -02:00
Rodrigo Damazio 95a21ffe8a Merging Jared's heads 2011-02-14 14:51:38 -02:00
David Piggott 89d2cf8340 Tidy up incorrect indentation resulting from Eclipse misconfiguration 2011-02-14 11:17:14 +00:00
David Piggott 0eb676d3cd Improve style and usability of ImportAllTracks
Fix style issues pointed out in code review. Move acquireWakeLock method into
MyTracksUtils and call that from ImportAllTracks and ExportAllTracks. Replace
important Toast messages with dialogs.
2011-02-14 10:53:37 +00:00
Jared Casper ac1a762fa8 Make GPX importer more robust.
Handle the case where the first track point does not have a time
without segfaulting. (issue #316, #192)

Don't throw an exception on a negative time change. It isn't the end
of the world, just put a warning in the log and move on (without
updating the speed, since we really don't have a clue now). (issue #186)

Reset the element content even when we aren't in a track element.  GPX
can have elements outside of the <trk>.  This was causing problems
such as content in a <metadata> element before the <trk> being
included in the track name.

There could still be weirdness in the stats if some of the points have
a time element while others don't.  Hard to say what the best course
of action is in that case though, and may involve changes to the
TripStatisticsBuilder.
2011-02-13 21:33:31 -08:00
David Piggott 59a05262a1 Issue 45: Add ImportAllTracks to import GPX files from /sdcard/MyTracks/gpx
ImportAllTracks is essentially just a modification of ExportAllTracks that
uses the existing GpxImporter to import all GPX files found in
/sdcard/MyTracks/gpx. So that the import functionality can always be accessed,
the MyTracks Activity's MenuManager is altered so that the Tracks button shows
regardless of whether there are any tracks in the database (ImportAllTracks is
launched by a button in the lower right of the MyTracksList activity).

A Toast message shows if no GPX files are found; this serves as a hint to
users on where to put GPX files for import so that this addition should be
discoverable and useable without documentation.

Im
2011-02-13 22:15:48 +00:00
Jared Casper 5f91d187c8 Merge upstream. 2011-02-12 15:49:23 -08:00
Matthew Simmons 8d8bf311ed Added tag v1.1.1 for changeset 98c38ee0ac32 2011-02-10 23:33:29 -05:00
Matthew Simmons 69545c8a56 Update for 1.1.1 release. 2011-02-10 19:05:52 -05:00
Matthew Simmons a1868c0205 Move drawable-mdpi/* to drawable so Cupcake can see them 2011-02-10 17:48:44 -05:00
Matthew Simmons 12bc4f0da8 Rename API feature check method 2011-02-10 16:29:00 -05:00
Matthew Simmons 510b887042 Issue 311: Don't reuse the SQLiteQueryBuilder on Cupcake or Donut
due to Android issue 1587
2011-02-10 15:58:01 -05:00
Matthew Simmons ff7171daa2 Parse the HTTP status code correctly 2011-02-09 21:06:20 -05:00
Jared Casper 5b0dbd5d30 Be more explicit in testing the XML output of GpxTrackWriter.
Instead of producing the comparison strings in the tester just as they
are produced in the tested code, use string literals, thus actually
testing that the formatting code is working as expected.
2011-02-09 16:23:55 -08:00
Jared Casper 9c7fb25a94 Be more explicit in testing the XML output of GpxTrackWriter.
Instead of producing the comparison strings in the tester just as they
are produced in the tested code, use string literals, thus actually
testing that the formatting code is working as expected.
2011-02-09 16:23:55 -08:00
Jared Casper 2e24eab5ad Addressing review comments. 2011-02-09 15:13:54 -08:00
Jared Casper 8135ad4424 Addressing review comments. 2011-02-09 15:13:54 -08:00
Jared Casper 6ffa40e40d Export GPX 1.1 instead of 1.0.
The only thing not allowed in 1.1 currently used is the track color
tag, which is moved into the "<extensions>" tag to make it conform to
the 1.1 schema.
2011-02-09 10:54:13 -08:00
Jared Casper bc120176ca Export GPX 1.1 instead of 1.0.
The only thing not allowed in 1.1 currently used is the track color
tag, which is moved into the "<extensions>" tag to make it conform to
the 1.1 schema.
2011-02-09 10:54:13 -08:00
Jared Casper d50d4ae1c9 Export GPX 1.1 instead of 1.0.
The only thing not allowed in 1.1 currently used is the track color
tag, which is moved into the "<extensions>" tag to make it conform to
the 1.1 schema.
2011-02-09 10:54:13 -08:00
Jared Casper d34aa61e95 Export GPX 1.1 instead of 1.0.
The only thing not allowed in 1.1 currently used is the track color
tag, which is moved into the "<extensions>" tag to make it conform to
the 1.1 schema.
2011-02-09 10:54:13 -08:00
Jared Casper 04578a4ed1 Reorder waypoint and track writing on export.
The GPX schema requires waypoints to come before the track.

Waypoints are not currently used in TCX export, so there are no
changes there.

In KML files, both tracks and waypoints are "Placemark"s, so it
doesn't matter what order they come in there.

This does change the order they appear in CSV files, which could
potentially invalidate some assumptions made in downstream tools.
2011-02-09 10:42:34 -08:00
Jared Casper 3e5282a06d Reorder waypoint and track writing on export.
The GPX schema requires waypoints to come before the track.

Waypoints are not currently used in TCX export, so there are no
changes there.

In KML files, both tracks and waypoints are "Placemark"s, so it
doesn't matter what order they come in there.

This does change the order they appear in CSV files, which could
potentially invalidate some assumptions made in downstream tools.
2011-02-09 10:42:34 -08:00
Jared Casper f86cadb802 Reorder waypoint and track writing on export.
The GPX schema requires waypoints to come before the track.

Waypoints are not currently used in TCX export, so there are no
changes there.

In KML files, both tracks and waypoints are "Placemark"s, so it
doesn't matter what order they come in there.

This does change the order they appear in CSV files, which could
potentially invalidate some assumptions made in downstream tools.
2011-02-09 10:42:34 -08:00
Jared Casper c4fe4d87db Reorder waypoint and track writing on export.
The GPX schema requires waypoints to come before the track.

Waypoints are not currently used in TCX export, so there are no
changes there.

In KML files, both tracks and waypoints are "Placemark"s, so it
doesn't matter what order they come in there.

This does change the order they appear in CSV files, which could
potentially invalidate some assumptions made in downstream tools.
2011-02-09 10:42:34 -08:00
Matthew Simmons 7e5deeaab3 merge 2011-02-09 13:41:15 -05:00
Matthew Simmons 36c425d699 Add HTTP status code to the error message 2011-02-09 13:27:20 -05:00
Jared Casper a2c69c6144 Clean up formatting of numbers in gpx export.
Addresses issue #299 and ensures Latitude and Longitude are formatted
correctly as well.

Also orders the elements in the waypoint correctly according to the
schema: <ele> and <time> need to come before <name> and <desc>.
2011-02-09 10:25:28 -08:00
Jared Casper 58e3c0604d Clean up formatting of numbers in gpx export.
Addresses issue #299 and ensures Latitude and Longitude are formatted
correctly as well.
2011-02-09 10:25:28 -08:00
Jared Casper 7408849eb5 Clean up formatting of numbers in gpx export.
Addresses issue #299 and ensures Latitude and Longitude are formatted
correctly as well.

Also orders the elements in the waypoint correctly according to the
schema: <ele> and <time> need to come before <name> and <desc>.
2011-02-09 10:25:28 -08:00
Jared Casper b42142f692 Clean up formatting of numbers in gpx export.
Addresses issue #299 and ensures Latitude and Longitude are formatted
correctly as well.

Also orders the elements in the waypoint correctly according to the
schema: <ele> and <time> need to come before <name> and <desc>.
2011-02-09 10:25:28 -08:00
Rodrigo Damazio ddafdcf392 Merging proguard changes. 2011-02-09 15:40:18 -02:00
Matthew Simmons 95cbaf1993 Add a better status message to the exception wrapper.
Be more paranoid about exception messages.
2011-02-09 11:34:46 -05:00
Sandor Dornbush 43aa3c857d Added tag v1.1.0 for changeset b5645347b00e 2011-02-08 20:05:33 -08:00
Sandor Dornbush 964e0d6f41 Update for 1.1.0 release. 2011-02-08 20:03:18 -08:00
Rodrigo Damazio 4d9bdd1c12 Fixing typo in Proguard config. 2011-02-01 21:46:45 -02:00
Rodrigo Damazio 6b0f1a0c10 Merging 2011-01-31 14:57:22 -02:00
Rodrigo Damazio 1cee590eca Adding some debugging information back 2011-01-31 14:56:54 -02:00
94 changed files with 3789 additions and 1818 deletions
+1
View File
@@ -5,3 +5,4 @@ syntax:glob
*/proguard/**
.metadata
MyTracksTest/annotations.log
MyTracks/src/com/google/android/apps/mytracks/io/mymaps
+1
View File
@@ -9,5 +9,6 @@
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
<classpathentry kind="src" path="MyTracksLib_src"/>
<classpathentry kind="lib" path="libs/libmymaps.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+8 -2
View File
@@ -17,8 +17,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.maps.mytracks"
android:installLocation="auto"
android:versionCode="22"
android:versionName="1.0.21">
android:versionCode="25"
android:versionName="1.1.2">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
@@ -56,6 +56,11 @@ limitations under the License.
android:label="MyTracks"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.MyTracksList"
android:label="Recorded Tracks" />
@@ -122,6 +127,7 @@ limitations under the License.
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.fusiontables" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.local" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.wise" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.writely" />
+17
View File
@@ -0,0 +1,17 @@
The libraries contained here in binary format are licensed according to their
own licenses, which allow their usage for the My Tracks application. Any other
applications that wish to use these files should consult the appropriate
licenses or the JAR's authors.
References are provided to the libraries:
accounts.jar (Android accounts library):
http://code.google.com/p/libs-for-android/wiki/AccountsLibrary
google-api-client-*.jar (GData client):
http://code.google.com/p/google-api-java-client/
google-common.jar,
googleloginclient-helper.jar,
libmymaps.jar (Google My Maps upload library):
Licensed exclusively for use in My Tracks and derived Android applications.
Binary file not shown.
+7 -1
View File
@@ -15,11 +15,17 @@
-dontwarn org.codehaus.**
-dontwarn com.google.appengine.**
# Make our stack traces useful
# Line numbers will be correct, file names will be replaced by "MT" since the
# class name is enough to get the file name.
-renamesourcefileattribute MT
-keepattributes SourceFile,LineNumberTable
# Keep everything android references from XMLs
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.app,backup.BackupAgent
-keep public class * extends android.app.backup.BackupAgent
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 384 B

After

Width:  |  Height:  |  Size: 384 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

+103
View File
@@ -0,0 +1,103 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apl/res/com.google.android.maps"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:layout_weight="0"
android:padding="3dip">
<TextView android:id="@+id/message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="16dip"
android:textColor="#fff"
android:text="@string/select_map_message"
android:layout_weight="0"/>
</LinearLayout>
<ListView android:id="@+id/maplist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<TextView android:id="@+id/mapslist_empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:gravity="center"
android:padding="3dip"
android:textSize="16dip"
android:textColor="#c00"
android:textStyle="bold"
android:text="@string/mapslist_no_maps_available"
android:visibility="gone"/>
<TextView android:id="@+id/failed"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="3dip"
android:textSize="16dip"
android:textColor="#c00"
android:textStyle="bold"
android:text="@string/maps_list_failed"
android:visibility="gone"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:layout_weight="0"
android:padding="3dip">
<LinearLayout android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="3dip"
android:layout_gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<ProgressBar
android:layout_height="20dip"
android:layout_width="20dip"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="3dip"
android:textSize="16dip"
android:textColor="#fff"
android:textStyle="bold"
android:text="@string/maps_list_loading"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
+6
View File
@@ -50,6 +50,12 @@ limitations under the License.
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button android:id="@+id/tracklist_btn_import_all"
android:layout_width="wrap_content"
android:layout_height="48dip"
android:layout_gravity="left|top"
android:text="@string/tracklist_btn_import_all"
android:textSize="14dip"/>
<Button android:id="@+id/tracklist_btn_export_all"
android:layout_width="wrap_content"
android:layout_height="48dip"
@@ -41,6 +41,39 @@ limitations under the License.
android:stretchColumns="1"
android:layout_marginTop="5dip"
android:layout_marginLeft="5dip">
<!-- My Maps -->
<TableRow>
<ImageView android:src="@drawable/mymaps_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dip">
<CheckBox android:id="@+id/sendtogoogle_google_mymaps"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_google_mymaps"
android:textSize="20dip"
android:checked="true"/>
<RadioGroup android:id="@+id/sendtogoogle_group_mymaps"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<RadioButton android:id="@+id/sendtogoogle_create_new_map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_create_new_map"/>
<RadioButton android:id="@+id/sendtogoogle_pick_existing_map"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/sendtogoogle_pick_existing_map"/>
</RadioGroup>
</LinearLayout>
</TableRow>
<!-- Fusion Tables -->
<TableRow>
<ImageView android:src="@drawable/googledocs_icon"
android:layout_width="wrap_content"
@@ -58,6 +91,8 @@ limitations under the License.
android:checked="true"/>
</LinearLayout>
</TableRow>
<!-- Google Docs -->
<TableRow>
<ImageView android:src="@drawable/googledocs_icon"
android:layout_width="wrap_content"
+8 -9
View File
@@ -54,7 +54,7 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">S\'ha enviat la ruta a maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">S\'ha enviat la ruta a un full de càlcul a docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">S\'ha enviat la ruta a docs.google.com.</string>
<string name="status_enable_gps">Piqueu aquí per anar als paràmetres de seguretat i ubicació i activeu \'habilita els satèl·lits GPS\'. Premeu el botó d\'anar enrere per tornar.</string>
<string name="status_enable_gps">Toqueu aquí per anar als paràmetres de seguretat i ubicació i activeu \'habilita els satèl·lits GPS\'. Premeu el botó d\'anar enrere per tornar.</string>
<string name="status_approximate_location">Nota: Es mostra una ubicació determinada a partir de les xarxes sense fils i antenes telefòniques.</string>
<string name="status_no_location">No es pot determinar la ubicació. Habiliteu més fonts d\'ubicació (vegeu els paràmetres de seguretat i ubicació) i assegureu-vos que el cel és visible. Proveu de nou més tard.</string>
<string name="status_waypoint_inserted">S\'ha inserit un marcader de punt de ruta.</string>
@@ -133,7 +133,6 @@ limitations under the License.
<string name="tracklist_all_tracks">Totes les rutes:</string>
<string name="tracklist_this_track">Aquesta ruta</string>
<string name="tracklist_share_track">Comparteix amb amics...</string>
<string name="tracklist_share_linkfusion">Un enllaç a Google Fusion Table</string>
<string name="tracklist_share_gpx_file">Un fitxer GPX</string>
<string name="tracklist_share_kml_file">Un fitxer KML</string>
<string name="tracklist_share_csv_file">Un fitxer CSV</string>
@@ -171,7 +170,7 @@ limitations under the License.
<string name="settings_not_available_summary">No es troba disponible a la vostra versió de l\'Android</string>
<string name="settings_announcement_frequency">Freqüència d\'avís</string>
<string name="settings_announcement_frequency_summary">Temps mínin entre dos avisos de veu</string>
<string name="settings_announcement_frequency_summary">Temps mínim entre dos avisos de veu</string>
<string name="settings_min_recording_distance">Distància mínima entre punts</string>
<string name="settings_min_recording_distance_summary">La distància més petita entre dos punts enregistrats de la vostra posició</string>
@@ -182,7 +181,7 @@ limitations under the License.
<string name="settings_min_recording_interval">Temps mínim entre punts</string>
<string name="settings_min_recording_interval_summary">Interval de temps mínim entre dos enregistraments de la vostra posició</string>
<string name="settings_min_required_accuracy">Precisió míima</string>
<string name="settings_min_required_accuracy">Precisió mínima</string>
<string name="settings_min_required_accuracy_summary">S\'aturarà l\'enregistrament si la vostra posició no és així de precisa com a mínim</string>
<string name="settings_metric_units">Fes servir unitats mètriques</string>
@@ -240,13 +239,13 @@ limitations under the License.
<!-- Sharing -->
<string name="share_map_subject">M\'agradaria compartir un mapa amb tu</string>
<string name="share_map_body_format">Crec que estaràs interessat en aquest mapa:
<string name="share_map_body_format">Em sembla que estaràs interessat en aquest mapa:
%s</string>
<string name="send_track_subject">M\'agradaria compartir una ruta amb tu</string>
<string name="send_track_body_format">Crec que estaràs interessat en aquesta ruta.
<string name="send_track_body_format">Em sembla que estaràs interessat en aquesta ruta.
Per verue aquesta ruta fes servir Google Earth o altre programari de mapes.</string>
Per veure aquesta ruta fes servir Google Earth o altre programari de mapes.</string>
<string name="share_track">Comparteix la ruta amb amics</string>
<!-- Visibility -->
@@ -256,8 +255,8 @@ limitations under the License.
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_label">Average Moving Speed</string>
<string name="average_moving_pace_label">Average Moving Pace</string>
<string name="average_moving_speed_label">Velocitat mitjana en moviment</string>
<string name="average_moving_pace_label">Ritme mitjà en moviment</string>
<!-- Average -->
<string name="average_speed_label">Velocitat mitjana</string>
+1 -2
View File
@@ -129,8 +129,7 @@ limitations under the License.
<string name="tracklist_pick_one">Einen Track auswählen. Lange berühren für mehr Optionen.</string>
<string name="tracklist_all_tracks">Alle Tracks</string>
<string name="tracklist_this_track">Dieser Track</string>
<string name="tracklist_share_track">Mit Freunden teilen…</string>
<string name="tracklist_share_linkfusion">Ein Link zur Google Fusion Tables-Karte</string>
<string name="tracklist_share_track">Mit Freunden teilen…</string>
<string name="tracklist_share_gpx_file">Eine GPX-Datei</string>
<string name="tracklist_share_kml_file">Eine KML-Datei</string>
<string name="tracklist_share_csv_file">Eine CSV-Datei</string>
-1
View File
@@ -47,7 +47,6 @@ limitations under the License.
<string name="tracklist_save_as_tcx">Guardar en archivo TCX</string>
<string name="tracklist_btn_send_all_to_fusiontables">Enviar a Google Fusion Tables</string>
<!-- Export All -->
<string name="tracklist_share_linkfusion">Un enlace a Google Fusion Tables</string>
<string name="tracklist_share_csv_file">Un archivo CSV</string>
<string name="tracklist_share_tcx_file">Un archivo TCX</string>
-1
View File
@@ -121,7 +121,6 @@ limitations under the License.
<string name="tracklist_all_tracks">Todos los recorridos:"</string>
<string name="tracklist_this_track">Este recorrido"</string>
<string name="tracklist_share_track">Compartir con amigos..."</string>
<string name="tracklist_share_linkfusion">Un enlace a Google Fusion Tables</string>
<string name="tracklist_share_gpx_file">Un archivo GPX"</string>
<string name="tracklist_share_kml_file">Un archivo KML"</string>
<string name="tracklist_clear_map">"Limpiar mapa"</string>
-1
View File
@@ -127,7 +127,6 @@ limitations under the License.
<string name="tracklist_all_tracks">Tous les parcours:</string>
<string name="tracklist_this_track">Ce parcours</string>
<string name="tracklist_share_track">Partager...</string>
<string name="tracklist_share_linkfusion">Lien sur Google Fusion Tables</string>
<string name="tracklist_share_gpx_file">Fichier GPX</string>
<string name="tracklist_share_kml_file">Fichier KML</string>
<string name="tracklist_clear_map">Effacer la carte</string>
+1 -1
View File
@@ -129,7 +129,7 @@ limitations under the License.
<string name="tracklist_all_tracks">Tutti i percorsi:</string>
<string name="tracklist_this_track">Questo percorso</string>
<string name="tracklist_share_track">Condividi con amici...</string>
<!-- <string name="tracklist_share_linkfusion">Un link alla mappa in «Le mie mappe» di Google</string> -->
<string name="tracklist_share_link">Un link alla mappa</string>
<string name="tracklist_share_gpx_file">Un file GPX</string>
<string name="tracklist_share_kml_file">Un file KML</string>
<string name="tracklist_clear_map">Cancella la mappa</string>
-1
View File
@@ -133,7 +133,6 @@ limitations under the License.
<string name="tracklist_all_tracks">全てのトラック:</string>
<string name="tracklist_this_track">このトラック</string>
<string name="tracklist_share_track">共有...</string>
<string name="tracklist_share_linkfusion">Google Fusion Tablesへのリンク</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>
-1
View File
@@ -131,7 +131,6 @@ limitations under the License.
<string name="tracklist_all_tracks">Wszystkie trasy:</string>
<string name="tracklist_this_track">Ta trasa</string>
<string name="tracklist_share_track">Podziel się z przyjaciółmi...</string>
<!-- <string name="tracklist_share_linkfusion">Link do Moich Map Google</string> -->
<string name="tracklist_share_gpx_file">Plik GPX</string>
<string name="tracklist_share_kml_file">PLik KML</string>
<string name="tracklist_share_csv_file">Plik CSV</string>
+27 -27
View File
@@ -18,9 +18,9 @@ limitations under the License.
<!-- Used by the Menu -->
<string name="start_recording">Gravar caminho</string>
<string name="stop_recording">Para gravação</string>
<string name="switch_to_sat">Modo do satélite"</string>
<string name="switch_to_map">Modo do mapa"</string>
<string name="mylocation">Minha Localização"</string>
<string name="switch_to_sat">Modo do satélite</string>
<string name="switch_to_map">Modo do mapa</string>
<string name="mylocation">Minha Localização</string>
<string name="list_tracks">Caminhos</string>
<string name="list_markers">Marcadores</string>
<string name="delete_all_tracks">Apagar todos os caminhos</string>
@@ -31,10 +31,10 @@ limitations under the License.
<string name="current_segment">Último segmento</string>
<!-- Error messages -->
<string name="error_no_location_manager">Gerenciador de localização não encontrado neste dispositivo!"</string>
<string name="error_no_gps_location_provider">"Provedor de posição de GPS não encontrado neste dispositivo."</string>
<string name="error_sending_to_fusiontables">Ocorreu um erro ao enviar seu caminho para o Google Fusion Tables. Por favor tente novamente mais tarde."</string>
<string name="error_sending_to_docs">Ocorreu um erro ao enviar seu caminho para o Google Docs. Por favor tente novamente mais tarde."</string>
<string name="error_no_location_manager">Gerenciador de localização não encontrado neste dispositivo!</string>
<string name="error_no_gps_location_provider">"Provedor de posição de GPS não encontrado neste dispositivo.</string>
<string name="error_sending_to_fusiontables">Ocorreu um erro ao enviar seu caminho para o Google Fusion Tables. Por favor tente novamente mais tarde.</string>
<string name="error_sending_to_docs">Ocorreu um erro ao enviar seu caminho para o Google Docs. Por favor tente novamente mais tarde.</string>
<string name="error_unable_to_start_recording">Impossível iniciar a gravação. Tente novamente.</string>
<string name="error_unable_to_stop_recording">Impossível interromper a gravação. Tente novamente.</string>
<string name="error_unable_to_read_file">Impossível ler do arquivo.</string>
@@ -52,7 +52,7 @@ limitations under the License.
<string name="status_tracks_have_been_uploaded">Seu caminho foi enviado para maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Seu caminho foi enviado para uma nova planilha em docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Seu caminho foi enviado para docs.google.com.</string>
<string name="status_enable_gps">"Por favor pressione aqui para ir aos ajustes de segurança e localização e verifique que o GPS esteja habilitado. Pressione o botão de voltar para retornar."</string>
<string name="status_enable_gps">"Por favor pressione aqui para ir aos ajustes de segurança e localização e verifique que o GPS esteja habilitado. Pressione o botão de voltar para retornar.</string>
<string name="status_approximate_location">Nota: Uma localização aproximada, determinada por torres de telefonia celular, está sendo mostrada.</string>
<string name="status_no_location">Sua localização não pôde ser determinada. Habilite fontes adicionais de localização (veja configurações de Segurança e Localização) e assegure-se de que o céu está visível.</string>
<string name="status_waypoint_inserted">Um marcador de ponto de passagem foi inserido.</string>
@@ -79,19 +79,19 @@ limitations under the License.
<string name="marker_will_be_permanently_deleted">O marcador será permanentemente apagado deste telefone.</string>
<!-- Text and links when uploading to Google My Maps -->
<string name="new_map_description">"Criado por My Tracks no Android."</string>
<string name="fusiontable_link">"Criado por &lt;a href='http://mytracks.appspot.com'&gt;My Tracks&lt;/a&gt; no Android."</string>
<string name="new_map_description">"Criado por My Tracks no Android.</string>
<string name="fusiontable_link">"Criado por &lt;a href='http://mytracks.appspot.com'&gt;My Tracks&lt;/a&gt; no Android.</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">Pontos</string>
<string name="recorded_date">Gravado"</string>
<string name="category">"Tipo de atividade"</string>
<string name="waypoint_category">"Tipo de marcador"</string>
<string name="part">"(Parte %d)"</string>
<string name="recorded_date">Gravado</string>
<string name="category">"Tipo de atividade</string>
<string name="waypoint_category">"Tipo de marcador</string>
<string name="part">"(Parte %d)</string>
<!-- Used by the track recording service -->
<string name="recording_your_track">"Gravando seu caminho..."</string>
<string name="new_track">"Caminho %d"</string>
<string name="recording_your_track">"Gravando seu caminho...</string>
<string name="new_track">"Caminho %d</string>
<!-- Common -->
<string name="no">Não</string>
@@ -122,22 +122,22 @@ limitations under the License.
<string name="tracklist_btn_send_all_to_fusiontables">Enviar para o Google Fusion Tables</string>
<string name="tracklist_btn_delete_all">Apagar todos os caminhos</string>
<string name="tracklist_pick_one">Escolha um caminho. Pressione&amp;segure para mais opções.</string>
<string name="tracklist_all_tracks">Todos os Caminhos:"</string>
<string name="tracklist_this_track">Este Caminho"</string>
<string name="tracklist_share_track">Compartilhar com amigos..."</string>
<string name="tracklist_share_linkfusion">Um link para a Google Fusion Table"</string>
<string name="tracklist_share_gpx_file">Um arquivo GPX"</string>
<string name="tracklist_share_kml_file">Um arquivo KML"</string>
<string name="tracklist_clear_map">"Limpar mapa"</string>
<string name="tracklist_all_tracks">Todos os Caminhos:</string>
<string name="tracklist_this_track">Este Caminho</string>
<string name="tracklist_share_track">Compartilhar com amigos...</string>
<string name="tracklist_share_link">Um link para a mapa</string>
<string name="tracklist_share_gpx_file">Um arquivo GPX</string>
<string name="tracklist_share_kml_file">Um arquivo KML</string>
<string name="tracklist_clear_map">Limpar mapa</string>
<!-- Waypoints list -->
<string name="waypointslist_btn_delete_all">Apagar todos os marcadores</string>
<string name="waypointslist_this_waypoint">Este Marcador</string>
<string name="waypointslist_delete_waypoint">Apagar</string>
<string name="waypointslist_edit_waypoint">Editar detalhes"</string>
<string name="waypointslist_show_waypoint">Mostrar no mapa"</string>
<string name="waypointslist_btn_insert_waypoint">Inserir ponto no caminho"</string>
<string name="waypointslist_btn_insert_statistics">Inserir estatísticas"</string>
<string name="waypointslist_edit_waypoint">Editar detalhes</string>
<string name="waypointslist_show_waypoint">Mostrar no mapa</string>
<string name="waypointslist_btn_insert_waypoint">Inserir ponto no caminho</string>
<string name="waypointslist_btn_insert_statistics">Inserir estatísticas</string>
<string name="waypointslist_pick_one">Escolha um marcador. Pressione&amp;segure para mais opções.</string>
<!-- MyTracks Details -->
-1
View File
@@ -131,7 +131,6 @@ limitations under the License.
<string name="tracklist_all_tracks">Alla spår:</string>
<string name="tracklist_this_track">Det här spåret</string>
<string name="tracklist_share_track">Dela med vänner...</string>
<!-- <string name="tracklist_share_linkfusion">En länk till Google Mina Kartor</string> -->
<string name="tracklist_share_gpx_file">En GPX-fil</string>
<string name="tracklist_share_kml_file">En KML-fil</string>
<string name="tracklist_share_csv_file">En CSV-fil</string>
-1
View File
@@ -132,7 +132,6 @@ limitations under the License.
<string name="tracklist_all_tracks">全部轨迹:</string>
<string name="tracklist_this_track">此轨迹</string>
<string name="tracklist_share_track">与朋友分享...</string>
<string name="tracklist_share_linkfusion">链接到 Google Fusion Table</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>
+6
View File
@@ -406,4 +406,10 @@ limitations under the License.
<item>@string/zephyr_sensor_type</item>
</array>
<!-- DO NOT TRANSLATE -->
<string-array name="ant_sensor_type_values">
<item>@string/ant_sensor_type</item>
<item>@string/srm_ant_bridge_sensor_type</item>
</string-array>
</resources>
+5
View File
@@ -36,6 +36,7 @@ limitations under the License.
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
<string name="send_to_docs_key">sendToDocs</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="send_to_fusion_tables_key">sendToFusionTables</string>
<string name="sensor_type_key">sensorType</string>
<string name="share_url_only_key">shareUrlOnly</string>
<string name="split_frequency_key">splitFrequency</string>
@@ -49,4 +50,8 @@ limitations under the License.
<!-- Keys for non-persisted preferences - DO NOT TRANSLATE -->
<string name="backup_to_sd_key">backupToSd</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<!-- Inoperative keys used for the location of preferences items -->
<string name="sensor_options_key">sensorOptions</string>
<string name="ant_options_key">antOptions</string>
</resources>
+50 -4
View File
@@ -35,6 +35,7 @@ limitations under the License.
<!-- Error messages -->
<string name="error_no_location_manager">No location manager was found on this device!</string>
<string name="error_no_gps_location_provider">No GPS location provider was found on this device.</string>
<string name="error_sending_to_mymap">There was an error while sending your track to Google My Map. Please try again later.</string>
<string name="error_sending_to_fusiontables">There was an error while sending your track to Google Fusion Tables. Please try again later.</string>
<string name="error_sending_to_docs">There was an error while sending your track to Google Docs. Please try again later.</string>
<string name="error_unable_to_start_recording">Unable to start recording. Try again.</string>
@@ -50,10 +51,12 @@ limitations under the License.
<string name="wait_for_fix">Waiting for a better GPS fix. Make sure the sky is visible.</string>
<string name="status_now_recording">Now recording a new track.</string>
<string name="status_stopped_recording">Stopped recording your track.</string>
<string name="status_new_mymap_has_been_created">A new Google My Map has been created at maps.google.com.</string>
<string name="status_new_fusiontable_has_been_created">A new Google Fusion table has been created at google.com/fusiontables.</string>
<string name="status_tracks_have_been_uploaded">Your track has been sent to maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Your track has been sent to a new spreadsheet at docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Your track has been sent to docs.google.com.</string>
<string name="status_mymap_info">It may sometimes take a few minutes before new tracks appear.</string>
<string name="status_enable_gps">Please tap here to go to Security and Location Settings and check \'enable GPS satellites\'. Press back button to return.</string>
<string name="status_approximate_location">Note: An approximate location as determined from cell towers and WIFI networks is shown.</string>
<string name="status_no_location">A location can currently not be determined. Enable more location sources (see Security and Location Settings) and make sure the sky is visible. Try again later.</string>
@@ -64,9 +67,13 @@ limitations under the License.
<string name="progress_title">Please wait...</string>
<string name="progress_message">Sending your track to Google.</string>
<string name="progress_message_reading_track">Reading track...</string>
<string name="progress_message_preparing_track">Preparing track...</string>
<string name="progress_message_authenticating_fusiontables">Authenticating to Google Fusion Tables...</string>
<string name="progress_message_authenticating_mymaps">Authenticating to Google Maps...</string>
<string name="progress_message_authenticating_docs">Authenticating to Google Docs...</string>
<string name="progress_message_sending_docs">Sending track to Google Docs...</string>
<string name="progress_message_sending_mymaps">Sending track to Google Maps...</string>
<string name="progress_message_creating_map">Creating new My Map...</string>
<string name="progress_message_sending_fusiontables">Sending track to Fusion Tables...</string>
<string name="progress_message_creating_fusiontable">Creating new Fusion table...</string>
<string name="import_progress_message">Importing a GPS track. This may take a while.</string>
@@ -83,6 +90,10 @@ limitations under the License.
<string name="new_map_description">Created by My Tracks on Android.</string>
<string name="fusiontable_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<!-- Text and links when uploading to Google My Maps -->
<string name="my_map_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<string name="new_map_title">New map</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">Points</string>
<string name="recorded_date">Recorded</string>
@@ -113,6 +124,8 @@ limitations under the License.
<!-- TrackList -->
<string name="tracklist_show_track">Show</string>
<string name="tracklist_send_to_mymaps">Send to Google My Maps</string>
<string name="tracklist_send_to_new_mymap">Send to New Google My Map</string>
<string name="tracklist_send_to_fusiontables">Send to Google Fusion Tables</string>
<string name="tracklist_send_to_new_fusiontable">Send to New Google Fusion Table</string>
<string name="tracklist_send_to_docs">Send to Google Docs</string>
@@ -125,20 +138,32 @@ limitations under the License.
<string name="tracklist_save_as_csv">Save a CSV file</string>
<string name="tracklist_save_as_tcx">Save a TCX file</string>
<string name="tracklist_btn_send_all_to_fusiontables">Send to Google Fusion Tables</string>
<string name="tracklist_btn_delete_all">Delete all tracks</string>
<string name="tracklist_btn_send_all_to_mymaps">Send to Google My Maps</string>
<string name="tracklist_btn_delete_all">Delete all</string>
<!-- Export All -->
<string name="tracklist_btn_export_all">Export all tracks</string>
<string name="tracklist_btn_export_all">Export all</string>
<string name="export_done">Export finished</string>
<string name="tracklist_pick_one">Pick a track. Press&amp;hold for more options.</string>
<string name="tracklist_all_tracks">All Tracks:</string>
<string name="tracklist_this_track">This Track</string>
<string name="tracklist_share_track">Share with Friends...</string>
<string name="tracklist_share_linkfusion">A link to Google Fusion Tables</string>
<string name="tracklist_share_link">A link to the map</string>
<string name="tracklist_share_gpx_file">A GPX file</string>
<string name="tracklist_share_kml_file">A KML file</string>
<string name="tracklist_share_csv_file">A CSV file</string>
<string name="tracklist_share_tcx_file">A TCX file</string>
<string name="tracklist_clear_map">Clear map</string>
<!-- Import All -->
<string name="tracklist_btn_import_all">Import all</string>
<string name="import_done">Import finished, imported</string>
<!-- Format:
$1 : Successful imports
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_done">Imported %1$d of %2$d GPX files from %3$s</string>
<string name="import_empty">No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.</string>
<string name="import_error">Import error for file %s</string>
<!-- Waypoints list -->
<string name="waypointslist_btn_delete_all">Delete all markers</string>
@@ -218,7 +243,7 @@ limitations under the License.
<string name="settings_sensor_type_summary">The type of sensor to connect to</string>
<string name="settings_heart_rate_ant_sensor_id">Clear ANT heart rate sensor pairing</string>
<string name="settings_power_ant_sensor_id">Clear ANT power sensor pairing</string>
<string name="settings_srm_bridge_ant_sensor_id">Clear ANT srm bridge sensor pairing</string>
<string name="settings_srm_bridge_ant_sensor_id">Clear ANT SRM bridge sensor pairing</string>
<string name="settings_ant_not_paired">ANT sensor not paired</string>
<string name="settings_ant_paired">ANT sensor paired with: %d</string>
<string name="bluetooth_sensor_title">Bluetooth sensor</string>
@@ -243,6 +268,23 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS Satellites</string>
<!-- My Maps -->
<string name="select_map_message">Pick a map from your account:</string>
<string name="create_new_map">Create new map...</string>
<string name="map_list_title">Send to Google My Maps</string>
<string name="maps_list_loading">Loading maps...</string>
<string name="maps_list_failed">Your maps could not be loaded at this time!</string>
<string name="open_map">Select this Map</string>
<string name="edit_map_details">Edit Map Details</string>
<string name="share_track">Share Track with Friends</string>
<string name="delete_map">Delete Map from your Account</string>
<string name="map_title">Map Title:</string>
<string name="map_description">Map Description:</string>
<string name="map_searchable_edit">Make this map and its contents show up in search results</string>
<string name="map_not_searchable_edit">Do not let this map or its contents show up in search results, though they may still be viewed be others through the direct URL</string>
<string name="map_searchable">This map and its contents will show in search results</string>
<string name="map_not_searchable">Neither this map nor its contents will show in search results, but they may still be viewed by others through a direct link</string>
<!-- Sharing -->
<string name="share_map">Share map with friends</string>
<string name="share_map_subject">I would like to share a track with you</string>
@@ -366,8 +408,11 @@ limitations under the License.
<!-- Send to Google -->
<string name="sendtogoogle_title">Send to...</string>
<string name="sendtogoogle_google_fusiontables">Google Fusion Tables</string>
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
<string name="sendtogoogle_google_docs">Google Docs</string>
<string name="sendtogoogle_send_now">Send now</string>
<string name="sendtogoogle_create_new_map">Create a new map</string>
<string name="sendtogoogle_pick_existing_map">Pick an existing map</string>
<string name="sendtogoogle_send_stats">Send track statistics</string>
<string name="sendtogoogle_send_stats_and_points">Send statistics and points</string>
@@ -419,6 +464,7 @@ limitations under the License.
<string name="bluetooth_pairing">Device pairing</string>
<string name="bluetooth_pairing_summary">Open Android\'s bluetooth settings to pair a device</string>
<string name="sensor_bluetooth_options_key">bluetoothSensorsEnabled</string>
<string name="ant_options">ANT options</string>
<string name="sensor_options">Sensor Options</string>
<!-- Bluetooth Sensor -->
+15 -12
View File
@@ -102,7 +102,8 @@ limitations under the License.
</PreferenceScreen>
<!-- Sensors -->
<PreferenceScreen android:title="@string/sensor_options">
<PreferenceScreen android:title="@string/sensor_options"
android:key="@string/sensor_options_key">
<ListPreference android:key="@string/sensor_type_key"
android:title="@string/settings_sensor_type"
android:summary="@string/settings_sensor_type_summary"
@@ -122,20 +123,22 @@ limitations under the License.
android:summary="@string/bluetooth_pairing_summary"
android:persistent="false" />
</PreferenceCategory>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_heart_rate_sensor_id_key"
android:title="@string/settings_heart_rate_ant_sensor_id"/>
<PreferenceCategory android:title="@string/ant_options"
android:key="@string/ant_options_key">
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_heart_rate_sensor_id_key"
android:title="@string/settings_heart_rate_ant_sensor_id"/>
<!--
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_power_sensor_id_key"
android:title="@string/settings_power_ant_sensor_id"/>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_power_sensor_id_key"
android:title="@string/settings_power_ant_sensor_id"/>
-->
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_srm_bridge_sensor_id_key"
android:title="@string/settings_srm_bridge_ant_sensor_id"/>
<com.google.android.apps.mytracks.AntPreference
android:key="@string/ant_srm_bridge_sensor_id_key"
android:title="@string/settings_srm_bridge_ant_sensor_id"/>
</PreferenceCategory>
</PreferenceScreen>
<!-- Sharing settings -->
-8
View File
@@ -28,14 +28,6 @@ package com.dsi.ant;
*/
public class AntDefine {
//////////////////////////////////////////////
// Library Version Information
//////////////////////////////////////////////
public static final int ANT_LIBRARY_VERSION_CODE = 5;
public static final int ANT_LIBRARY_VERSION_MAJOR = 1;
public static final int ANT_LIBRARY_VERSION_MINOR = 4;
public static final String ANT_LIBRARY_VERSION_NAME = String.valueOf(ANT_LIBRARY_VERSION_MAJOR) + "." + String.valueOf(ANT_LIBRARY_VERSION_MINOR);
//////////////////////////////////////////////
// Valid Configuration Values
//////////////////////////////////////////////
File diff suppressed because it is too large Load Diff
@@ -22,13 +22,16 @@ package com.dsi.ant;
*/
public interface AntInterfaceIntent {
public static final String STATUS = "com.dsi.ant.rx.intent.STATUS";
public static final String ANT_MESSAGE = "com.dsi.ant.intent.ANT_MESSAGE";
public static final String STATUS = "com.dsi.ant.rx.intent.STATUS";
public static final String ANT_MESSAGE = "com.dsi.ant.intent.ANT_MESSAGE";
public static final String ANT_INTERFACE_CLAIMED_PID = "com.dsi.ant.intent.ANT_INTERFACE_CLAIMED_PID";
public static final String ANT_ENABLED_ACTION = "com.dsi.ant.intent.action.ANT_ENABLED";
public static final String ANT_DISABLED_ACTION = "com.dsi.ant.intent.action.ANT_DISABLED";
public static final String ANT_RESET_ACTION = "com.dsi.ant.intent.action.ANT_RESET";
public static final String ANT_INTERFACE_CLAIMED_ACTION = "com.dsi.ant.intent.action.ANT_INTERFACE_CLAIMED_ACTION";
public static final String ANT_RX_MESSAGE_ACTION = "com.dsi.ant.intent.action.ANT_RX_MESSAGE_ACTION";
}
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright 2011 Dynastream Innovations 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.dsi.ant;
/**
* The Android Ant API is not finalized, and *will* be updated and expanded.
* This is the base level ANT messaging API and gives any application full
* control over the ANT radio HW. Caution should be exercised when using
* this interface.
*
* Public API for controlling the Ant Service.
*
* {@hide}
*/
interface IAnt_6 {
// Since version 1 (1.0):
boolean enable();
boolean disable();
boolean isEnabled();
boolean ANTTxMessage(in byte[] message);
boolean ANTResetSystem();
boolean ANTUnassignChannel(byte channelNumber);
boolean ANTAssignChannel(byte channelNumber, byte channelType, byte networkNumber);
boolean ANTSetChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType);
boolean ANTSetChannelPeriod(byte channelNumber, int channelPeriod);
boolean ANTSetChannelRFFreq(byte channelNumber, byte radioFrequency);
boolean ANTSetChannelSearchTimeout(byte channelNumber, byte searchTimeout);
boolean ANTSetLowPriorityChannelSearchTimeout(byte channelNumber, byte searchTimeout);
boolean ANTSetProximitySearch(byte channelNumber, byte searchThreshold);
boolean ANTSetChannelTxPower(byte channelNumber, byte txPower);
boolean ANTAddChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType, byte listIndex);
boolean ANTConfigList(byte channelNumber, byte listSize, byte exclude);
boolean ANTOpenChannel(byte channelNumber);
boolean ANTCloseChannel(byte channelNumber);
boolean ANTRequestMessage(byte channelNumber, byte messageID);
boolean ANTSendBroadcastData(byte channelNumber, in byte[] txBuffer);
boolean ANTSendAcknowledgedData(byte channelNumber, in byte[] txBuffer);
boolean ANTSendBurstTransferPacket(byte control, in byte[] txBuffer);
int ANTSendBurstTransfer(byte channelNumber, in byte[] txBuffer);
int ANTTransmitBurst(byte channelNumber, in byte[] txBuffer, int initialPacket, boolean containsEndOfBurst);
// Since version 4 (1.3):
boolean ANTConfigEventBuffering(int screenOnFlushTimerInterval, int screenOnFlushBufferThreshold, int screenOffFlushTimerInterval, int screenOffFlushBufferThreshold);
// Since version 2 (1.1):
boolean ANTDisableEventBuffering();
// Since version 3 (1.2):
int getServiceLibraryVersionCode();
String getServiceLibraryVersionName();
// Since version 6 (1.5):
boolean claimInterface();
boolean requestForceClaimInterface(String appName);
boolean stopRequestForceClaimInterface();
boolean releaseInterface();
boolean hasClaimedInterface();
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright 2011 Dynastream Innovations 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.dsi.ant;
/**
* Defines version numbers
*
* @hide
*/
public class Version {
//////////////////////////////////////////////
// Library Version Information
//////////////////////////////////////////////
public static final int ANT_LIBRARY_VERSION_CODE = 6;
public static final int ANT_LIBRARY_VERSION_MAJOR = 2;
public static final int ANT_LIBRARY_VERSION_MINOR = 0;
public static final String ANT_LIBRARY_VERSION_NAME = String.valueOf(ANT_LIBRARY_VERSION_MAJOR) + "." + String.valueOf(ANT_LIBRARY_VERSION_MINOR);
}
@@ -0,0 +1,20 @@
package com.dsi.ant.exception;
public class AntInterfaceException extends Exception
{
/**
*
*/
private static final long serialVersionUID = -7278855366167722274L;
public AntInterfaceException()
{
this("Unknown ANT Interface error");
}
public AntInterfaceException(String string)
{
super(string);
}
}
@@ -0,0 +1,23 @@
package com.dsi.ant.exception;
import android.os.RemoteException;
public class AntRemoteException extends AntInterfaceException
{
/**
*
*/
private static final long serialVersionUID = 8950974759973459561L;
public AntRemoteException(RemoteException e)
{
this("ANT Interface error, ANT Radio Service remote error: "+e.toString(), e);
}
public AntRemoteException(String string, RemoteException e)
{
super(string);
this.initCause(e);
}
}
@@ -0,0 +1,19 @@
package com.dsi.ant.exception;
public class AntServiceNotConnectedException extends AntInterfaceException
{
/**
*
*/
private static final long serialVersionUID = -2085081032170129309L;
public AntServiceNotConnectedException()
{
this("ANT Interface error, ANT Radio Service not connected.");
}
public AntServiceNotConnectedException(String string)
{
super(string);
}
}
@@ -88,7 +88,10 @@ public class DialogManager {
builder.setNeutralButton(activity.getString(R.string.share_track),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
activity.shareLinkToFusionTable(activity.sendToTrackId);
SendToGoogleDialog sendToGoogleDialog = getSendToGoogleDialog();
final boolean sentToMyMaps = sendToGoogleDialog.getSendToMyMaps();
final boolean sentToFusionTables = sendToGoogleDialog.getSendToFusionTables();
activity.shareLinkToMap(sentToMyMaps, sentToFusionTables);
dialog.dismiss();
}
});
@@ -119,9 +122,11 @@ public class DialogManager {
sendToGoogleResultDialog.setIcon(success
? android.R.drawable.ic_dialog_info
: android.R.drawable.ic_dialog_alert);
sendToGoogleResultDialog.setMessage(activity.getFusionTablesResultMessage());
sendToGoogleResultDialog.setMessage(activity.getSendToGoogleResultMessage());
boolean canShare = activity.getSendToFusionTablesTableId() != null;
boolean canShare =
activity.getSendToFusionTablesTableId() != null &&
activity.getSendToMyMapsMapId() != null;
View share =
sendToGoogleResultDialog.findViewById(android.R.id.button3);
if (share != null) {
@@ -20,18 +20,17 @@ import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.TrackWriter;
import com.google.android.apps.mytracks.io.TrackWriterFactory;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
@@ -121,7 +120,7 @@ public class ExportAllTracks {
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
acquireWakeLock();
wakeLock = MyTracksUtils.acquireWakeLock(activity, wakeLock);
}
// Now we can safely export everything.
@@ -211,40 +210,4 @@ public class ExportAllTracks {
}
}
}
/**
* Tries to acquire a partial wake lock if not already acquired. Logs errors
* and gives up trying in case the wake lock cannot be acquired.
*/
private void acquireWakeLock() {
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
try {
PowerManager pm =
(PowerManager) activity.getSystemService(Context.POWER_SERVICE);
if (pm == null) {
Log.e(MyTracksConstants.TAG,
"ExportAllTracks: Power manager not found!");
return;
}
if (wakeLock == null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
MyTracksConstants.TAG);
if (wakeLock == null) {
Log.e(MyTracksConstants.TAG,
"ExportAllTracks: Could not create wake lock (null).");
return;
}
}
if (!wakeLock.isHeld()) {
wakeLock.acquire();
if (!wakeLock.isHeld()) {
Log.e(MyTracksConstants.TAG,
"ExportAllTracks: Could not acquire wake lock.");
}
}
} catch (RuntimeException e) {
Log.e(MyTracksConstants.TAG,
"ExportAllTracks: Caught unexpected exception: " + e.getMessage(), e);
}
}
}
@@ -0,0 +1,217 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.io.GpxImporter;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* A class that will import all GPX tracks in /sdcard/MyTracks/gpx/
*
* @author David Piggott
*/
public class ImportAllTracks {
private final Activity activity;
private WakeLock wakeLock;
private ProgressDialog progress;
private FileUtils fileUtils;
private String gpxPath;
private int gpxFileCount;
private int importSuccessCount;
public ImportAllTracks(Activity activity) {
this.activity = activity;
Log.i(MyTracksConstants.TAG, "ImportAllTracks: Starting");
fileUtils = new FileUtils();
gpxPath = fileUtils.buildExternalDirectoryPath("gpx");
HandlerThread handlerThread;
Handler handler;
handlerThread = new HandlerThread("ImportAllTracks");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(runner);
}
private final Runnable runner = new Runnable() {
public void run() {
aquireLocksAndImport();
}
};
/**
* Makes sure that we keep the phone from sleeping. See if there is a current
* track. Acquire a wake lock if there is no current track.
*/
private void aquireLocksAndImport() {
SharedPreferences prefs = activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
long recordingTrackId = -1;
if (prefs != null) {
recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
wakeLock = MyTracksUtils.acquireWakeLock(activity, wakeLock);
}
// Now we can safely import everything.
importAll();
// Release the wake lock if we acquired one.
// TODO check what happens if we started recording after getting this lock.
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
Log.i(MyTracksConstants.TAG, "ImportAllTracks: Releasing wake lock.");
}
Log.i(MyTracksConstants.TAG, "ImportAllTracks: Done");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (gpxFileCount == 0) {
builder.setMessage(activity.getString(R.string.import_empty, gpxPath + "/"));
} else {
builder.setMessage(activity.getString(R.string.import_done, importSuccessCount, gpxFileCount,
gpxPath + "/"));
}
builder.setPositiveButton(R.string.ok, null);
builder.show();
}
private void makeProgressDialog(final int trackCount) {
String importMsg = activity.getString(R.string.tracklist_btn_import_all);
progress = new ProgressDialog(activity);
progress.setIcon(android.R.drawable.ic_dialog_info);
progress.setTitle(importMsg);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setMax(trackCount);
progress.setProgress(0);
progress.show();
}
/**
* Actually import the tracks. This should be called after the wake locks have
* been acquired.
*/
private void importAll() {
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(activity);
if (!fileUtils.isSdCardAvailable()) {
return;
}
List<File> gpxFiles = getGpxFiles();
gpxFileCount = gpxFiles.size();
if (gpxFileCount == 0) {
return;
}
Log.i(MyTracksConstants.TAG, "ImportAllTracks: Importing: " + gpxFileCount + " tracks.");
activity.runOnUiThread(new Runnable() {
public void run() {
makeProgressDialog(gpxFileCount);
}
});
Iterator<File> gpxFilesIterator = gpxFiles.iterator();
for (int currentFileNumber = 0; gpxFilesIterator.hasNext(); currentFileNumber++) {
File currentFile = gpxFilesIterator.next();
final int status = currentFileNumber;
activity.runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progress == null) {
return;
}
progress.setProgress(status);
}
}
});
if (importFile(currentFile, providerUtils)) {
importSuccessCount++;
}
}
if (progress != null) {
synchronized (this) {
progress.dismiss();
progress = null;
}
}
}
/**
* Attempts to import a GPX file. Returns true on success, issues error
* notifications and returns false on failure.
*/
private boolean importFile(File gpxFile, MyTracksProviderUtils providerUtils) {
Log.i(MyTracksConstants.TAG, "ImportAllTracks: importing: " + gpxFile.getName());
try {
GpxImporter.importGPXFile(new FileInputStream(gpxFile), providerUtils);
return true;
} catch (FileNotFoundException e) {
Log.w(MyTracksConstants.TAG, "GPX file wasn't found/went missing: "
+ gpxFile.getAbsolutePath(), e);
} catch (ParserConfigurationException e) {
Log.w(MyTracksConstants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e);
} catch (SAXException e) {
Log.w(MyTracksConstants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e);
} catch (IOException e) {
Log.w(MyTracksConstants.TAG, "Error reading file: " + gpxFile.getAbsolutePath(), e);
}
Toast.makeText(activity, activity.getString(R.string.import_error, gpxFile.getName()),
Toast.LENGTH_LONG).show();
return false;
}
/**
* Returns a list of the GPX Files found in the GPX directory.
*/
private List<File> getGpxFiles() {
List<File> gpxFiles = new LinkedList<File>();
File[] gpxFileCandidates = new File(gpxPath).listFiles();
if (gpxFileCandidates != null) {
for (File file : gpxFileCandidates) {
if (!file.isDirectory() && file.getName().endsWith(".gpx")) {
gpxFiles.add(file);
}
}
}
return gpxFiles;
}
}
@@ -42,7 +42,7 @@ class MenuManager {
public void onPrepareOptionsMenu(Menu menu, boolean hasRecorded,
boolean isRecording, boolean hasSelectedTrack) {
menu.findItem(R.id.menu_list_tracks).setEnabled(hasRecorded);
menu.findItem(R.id.menu_list_tracks);
menu.findItem(R.id.menu_list_markers)
.setEnabled(hasRecorded && hasSelectedTrack);
menu.findItem(R.id.menu_start_recording)
@@ -0,0 +1,210 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.accounts.Account;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.MyMapsFactory;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.mymaps.MapsService;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
/**
* Activity which displays the list of current My Maps tracks for the user.
* Returns RESULT_OK if the user picked a map, and returns "mapid" as an extra.
*
* @author Rodrigo Damazio
*/
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
private static final int MENU_OPEN = 0;
private static final int MENU_SHARE = 2;
private static final int GET_LOGIN = 1;
private MapsFacade mapsClient;
private AuthManager auth;
private MyMapsListAdapter listAdapter;
private int contextPosition;
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
menu.add(0, MENU_OPEN, 0, R.string.open_map);
menu.add(0, MENU_SHARE, 0, R.string.share_map);
}
};
private final OnItemClickListener clickListener =
new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position,
long id) {
Intent result = new Intent();
result.putExtra("mapid", (String) listAdapter.getItem(position));
setResult(RESULT_OK, result);
finish();
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
auth = AuthManagerFactory.getAuthManager(this, GET_LOGIN, null, true,
MapsService.getServiceName());
setContentView(R.layout.list);
listAdapter = new MyMapsListAdapter(this);
ListView list = (ListView) findViewById(R.id.maplist);
list.setOnItemClickListener(clickListener);
list.setOnCreateContextMenuListener(contextMenuListener);
list.setAdapter(listAdapter);
startLogin();
}
private void startLogin() {
// Starts in the UI thread.
// TODO fix this for non-froyo devices.
if (AuthManagerFactory.useModernAuthManager()) {
MyTracks.getInstance().getAccountChooser().chooseAccount(
MyMapsList.this,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
// Account selection happens in the UI thread.
if (account != null) {
// The user did not quit and there was a valid google
// account.
doLogin(account);
}
}
});
} else {
doLogin(null);
}
}
private void doLogin(final Account account) {
// Starts in the UI thread.
auth.doLogin(new Runnable() {
public void run() {
// Runs in UI thread.
mapsClient = MyMapsFactory.newMapsClient(MyMapsList.this, auth);
startLookup();
}
}, account);
}
private void startLookup() {
// Starts in the UI thread.
new Thread() {
@Override
public void run() {
// Communication with Maps happens in its own thread.
// This will call onReceivedMapListing below.
final boolean success = mapsClient.getMapsList(MyMapsList.this);
runOnUiThread(new Runnable() {
@Override
public void run() {
// Updating the UI when done happens in the UI thread.
onLookupDone(success);
}
});
}
}.start();
}
@Override
public void onReceivedMapListing(final String mapId, final String title,
final String description, final boolean isPublic) {
// Starts in the communication thread.
runOnUiThread(new Runnable() {
@Override
public void run() {
// Updating the list with new contents happens in the UI thread.
listAdapter.addMapListing(mapId, title, description, isPublic);
}
});
}
private void onLookupDone(boolean success) {
// Starts in the UI thread.
findViewById(R.id.loading).setVisibility(View.GONE);
if (!success) {
findViewById(R.id.failed).setVisibility(View.VISIBLE);
}
TextView emptyView = (TextView) findViewById(R.id.mapslist_empty);
ListView list = (ListView) findViewById(R.id.maplist);
list.setEmptyView(emptyView);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == GET_LOGIN) {
auth.authResult(resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case MENU_OPEN:
clickListener.onItemClick(null, null, contextPosition, 0);
return true;
case MENU_SHARE:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getText(R.string.share_map_subject));
String[] listItem = (String[]) listAdapter.getMapListingArray(contextPosition);
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
getText(R.string.share_map_body_format).toString(),
listItem[1],
MapsService.buildMapUrl(listItem[0])));
startActivity(Intent.createChooser(shareIntent,
getText(R.string.share_map).toString()));
return true;
}
return false;
}
}
@@ -0,0 +1,135 @@
package com.google.android.apps.mytracks;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.TextView;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;
public class MyMapsListAdapter implements ListAdapter {
private Vector<String[]> mapsList;
private Vector<Boolean> publicList;
private Set<DataSetObserver> observerSet;
private final Activity activity;
public MyMapsListAdapter(Activity activity) {
this.activity = activity;
mapsList = new Vector<String[]>();
publicList = new Vector<Boolean>();
observerSet = new HashSet<DataSetObserver>();
}
public void addMapListing(String mapId, String title,
String description, boolean isPublic) {
synchronized (mapsList) {
// Search through the maps list to see if it has the mapid and
// remove it if so, so that we can replace it with updated info
for (int i = 0; i < mapsList.size(); ++i) {
if (mapId.equals(mapsList.get(i)[0])) {
mapsList.remove(i);
publicList.remove(i);
--i;
}
}
mapsList.add(new String[] { mapId, title, description });
publicList.add(isPublic);
}
Iterator<DataSetObserver> iter = observerSet.iterator();
while (iter.hasNext()) {
iter.next().onChanged();
}
}
public String[] getMapListingArray(int position) {
return mapsList.get(position);
}
@Override
public boolean areAllItemsEnabled() {
return true;
}
@Override
public boolean isEnabled(int position) {
return true;
}
@Override
public int getCount() {
return mapsList.size();
}
@Override
public Object getItem(int position) {
return mapsList.get(position)[0];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView =
activity.getLayoutInflater().inflate(R.layout.listitem, parent, false);
}
String[] map = mapsList.get(position);
((TextView) convertView.findViewById(R.id.maplistitem)).setText(map[1]);
((TextView) convertView.findViewById(R.id.maplistdesc)).setText(map[2]);
TextView publicUnlisted =
(TextView) convertView.findViewById(R.id.maplistpublic);
if (publicList.get(position)) {
publicUnlisted.setTextColor(Color.RED);
publicUnlisted.setText(R.string.public_map);
} else {
publicUnlisted.setTextColor(Color.GREEN);
publicUnlisted.setText(R.string.unlisted_map);
}
return convertView;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isEmpty() {
return mapsList.isEmpty();
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
observerSet.add(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
observerSet.remove(observer);
}
}
@@ -29,11 +29,13 @@ import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.GpxImporter;
import com.google.android.apps.mytracks.io.SendToDocs;
import com.google.android.apps.mytracks.io.SendToFusionTables;
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.SendToMyMaps;
import com.google.android.apps.mytracks.io.TempFileCleaner;
import com.google.android.apps.mytracks.io.TrackWriter;
import com.google.android.apps.mytracks.io.TrackWriterFactory;
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.io.mymaps.MapsService;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.services.TrackRecordingService;
@@ -62,14 +64,15 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.RelativeLayout;
import android.widget.TabHost;
import android.widget.Toast;
@@ -122,8 +125,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
*/
public long sendToTrackId = -1;
public boolean sendToMyMapsSuccess = false;
public boolean sendToFusionTablesSuccess = false;
public boolean sendToDocsSuccess = false;
public String sendToMyMapsMapId;
public String sendToMyMapsMessage = "";
public String sendToFusionTablesTableId;
public String sendToFusionTablesMessage = "";
public String sendToDocsMessage = "";
@@ -446,27 +452,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
final Intent results) {
TrackFileFormat exportFormat = null;
switch (requestCode) {
case MyTracksConstants.GET_MAP: {
if (resultCode == RESULT_OK) {
results.putExtra("trackid", selectedTrackId);
if (results.hasExtra("tableid")) {
sendToFusionTablesTableId = results.getStringExtra("tableid");
}
setProgressMessage(
R.string.progress_message_authenticating_fusiontables);
authenticate(results, MyTracksConstants.SEND_TO_GOOGLE,
SendToFusionTables.SERVICE_ID);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
break;
}
case MyTracksConstants.GET_LOGIN: {
if (resultCode == RESULT_OK && auth != null) {
if (!auth.authResult(resultCode, results)) {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
} else {
if (resultCode != RESULT_OK || auth == null || !auth.authResult(resultCode, results)) {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
break;
@@ -515,67 +502,48 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_DOCLIST: {
if (resultCode == RESULT_OK) {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(results,
MyTracksConstants.AUTHENTICATE_TO_TRIX,
SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_TRIX: {
if (resultCode == RESULT_OK) {
setProgressValue(30);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(results, MyTracksConstants.SEND_TO_DOCS,
SendToDocs.GDATA_SERVICE_NAME_TRIX);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
break;
}
case MyTracksConstants.SEND_TO_DOCS: {
if (results != null && resultCode == RESULT_OK) {
Log.d(MyTracksConstants.TAG, "Sending to Docs....");
setProgressValue(50);
setProgressMessage(R.string.progress_message_sending_docs);
final long trackId = results.getLongExtra("trackid", selectedTrackId);
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
sendToTrackId = trackId;
Runnable onCompletion = new Runnable() {
public void run() {
setProgressValue(100);
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
runOnUiThread(new Runnable() {
public void run() {
sendToDocsMessage = sender.getStatusMessage();
sendToDocsSuccess = sender.wasSuccess();
handleFusionTablesFinish(trackId);
}
});
}
};
sender.setOnCompletion(onCompletion);
sender.run();
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
}
break;
}
case MyTracksConstants.SEND_TO_GOOGLE_DIALOG: {
shareRequested = false;
shareRequested = false;
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
break;
}
case MyTracksConstants.SEND_TO_GOOGLE: {
case MyTracksConstants.GET_MAP: {
// User picked a map to upload to
if (resultCode == RESULT_OK) {
results.putExtra("trackid", selectedTrackId);
if (results.hasExtra("mapid")) {
sendToMyMapsMapId = results.getStringExtra("mapid");
}
authenticateToGoogleMaps(results);
} else {
onSendToGoogleDone();
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_MY_MAPS: {
// Authenticated with Google My Maps
if (results != null && resultCode == RESULT_OK) {
final String mapId;
final long trackId;
if (results.hasExtra("mapid")) {
mapId = results.getStringExtra("mapid");
} else {
mapId = "new";
}
if (results.hasExtra("trackid")) {
trackId = results.getLongExtra("trackid", -1);
} else {
trackId = selectedTrackId;
}
sendToGoogleMaps(trackId, mapId);
} else {
onSendToGoogleDone();
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_FUSION_TABLES: {
// Authenticated with Google Fusion Tables
if (results != null && resultCode == RESULT_OK) {
final long trackId;
if (results.hasExtra("trackid")) {
@@ -583,48 +551,29 @@ public class MyTracks extends TabActivity implements OnTouchListener,
} else {
trackId = selectedTrackId;
}
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
@Override
public void onSendCompleted(String tableId, boolean success,
int statusMessage) {
sendToFusionTablesMessage = getString(statusMessage);
sendToFusionTablesSuccess = success;
if (sendToFusionTablesSuccess) {
sendToFusionTablesTableId = tableId;
// Update the table id for this track:
try {
Track track = providerUtils.getTrack(trackId);
track.setTableId(tableId);
providerUtils.updateTrack(track);
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(MyTracksConstants.TAG, "Updating table id failed.", e);
}
}
if (dialogManager.getSendToGoogleDialog().getSendToDocs()) {
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST,
RESULT_OK, new Intent());
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
runOnUiThread(new Runnable() {
public void run() {
handleFusionTablesFinish(trackId);
}
});
}
}
};
sendToTrackId = trackId;
final SendToFusionTables sender = new SendToFusionTables(this, auth,
trackId, this/*progressIndicator*/, onCompletion);
HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(sender);
sendToFusionTables(trackId);
} else {
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
onSendToGoogleDone();
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_DOCLIST: {
// Authenticated with Google Docs
if (resultCode == RESULT_OK) {
authenticateToGoogleTrix();
} else {
onSendToGoogleDone();
}
break;
}
case MyTracksConstants.AUTHENTICATE_TO_TRIX: {
// Authenticated with Trix
if (resultCode == RESULT_OK) {
final long trackId = results.getLongExtra("trackid", selectedTrackId);
sendToGoogleDocs(trackId);
} else {
onSendToGoogleDone();
}
break;
}
@@ -650,8 +599,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
case MyTracksConstants.SHARE_LINK: {
Track selectedTrack = providerUtils.getTrack(selectedTrackId);
if (selectedTrack != null) {
if (selectedTrack.getTableId().length() > 0) {
shareLinkToFusionTable(selectedTrackId);
if (!TextUtils.isEmpty(selectedTrack.getMapId())) {
shareLinkToMap(MapsService.buildMapUrl(selectedTrack.getMapId()));
} else if (!TextUtils.isEmpty(selectedTrack.getTableId())) {
shareLinkToMap(getFusionTablesUrl(selectedTrackId));
} else {
shareRequested = true;
dialogManager.showDialogSafely(DIALOG_SEND_TO_GOOGLE);
@@ -720,6 +671,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Resets status information for sending to MyMaps/Docs.
*/
public void resetSendToGoogleStatus() {
sendToMyMapsMapId = null;
sendToMyMapsMessage = "";
sendToMyMapsSuccess = true;
sendToFusionTablesMessage = "";
sendToFusionTablesSuccess = true;
sendToDocsMessage = "";
@@ -792,10 +746,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
/**
* Shares a link to a fusion table via external app (email, gmail, ...)
* Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
* A chooser with apps that support text/plain will be shown to the user.
*/
public void shareLinkToFusionTable(long trackId) {
public void shareLinkToMap(String url) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
@@ -807,8 +761,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
getString(R.string.share_url_only_key), false);
}
Track track = providerUtils.getTrack(trackId);
String url = SendToFusionTables.getMapVisualizationUrl(track);
String msg = shareUrlOnly ? url : String.format(
getResources().getText(R.string.share_map_body_format).toString(), url);
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
@@ -1027,6 +979,8 @@ public class MyTracks extends TabActivity implements OnTouchListener,
* Initiates the process to send tracks to google.
* This is called once the user has selected sending options via the
* SendToGoogleDialog.
*
* TODO: Change this whole flow to an actual state machine.
*/
public void sendToGoogle() {
SendToGoogleDialog sendToGoogleDialog =
@@ -1037,17 +991,253 @@ public class MyTracks extends TabActivity implements OnTouchListener,
setProgressValue(0);
clearProgressMessage();
dialogManager.showDialogSafely(DIALOG_PROGRESS);
if (sendToGoogleDialog.getSendToFusionTables()) {
setProgressValue(0);
setProgressMessage(R.string.progress_message_authenticating_fusiontables);
authenticate(new Intent(), MyTracksConstants.SEND_TO_GOOGLE,
SendToFusionTables.SERVICE_ID);
} else {
onActivityResult(MyTracksConstants.AUTHENTICATE_TO_DOCLIST, RESULT_OK,
new Intent());
if (sendToGoogleDialog.getSendToMyMaps()) {
sendToGoogleMapsOrPickMap(sendToGoogleDialog);
} else if (sendToGoogleDialog.getSendToFusionTables()) {
authenticateToFusionTables(null);
} else if (sendToGoogleDialog.getSendToDocs()) {
authenticateToGoogleDocs();
} else {
Log.w(MyTracksConstants.TAG, "Nowhere to upload to");
onSendToGoogleDone();
}
}
private void sendToGoogleMapsOrPickMap(SendToGoogleDialog sendToGoogleDialog) {
if (!sendToGoogleDialog.getCreateNewMap()) {
// Ask the user to choose a map to upload into
Intent listIntent = new Intent(this, MyMapsList.class);
startActivityForResult(listIntent, MyTracksConstants.GET_MAP);
// The callback for GET_MAP calls authenticateToGoogleMaps
} else {
authenticateToGoogleMaps(null);
}
}
private void authenticateToGoogleMaps(Intent results) {
if (results == null) { results = new Intent(); }
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_mymaps);
authenticate(results, MyTracksConstants.AUTHENTICATE_TO_MY_MAPS,
MapsService.getServiceName());
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
}
private void sendToGoogleMaps(final long trackId, String mapId) {
SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
@Override
public void onSendCompleted(String mapId, boolean success, int statusMessage) {
sendToMyMapsMessage = getString(statusMessage);
sendToMyMapsSuccess = success;
if (sendToMyMapsSuccess) {
sendToMyMapsMapId = mapId;
// Update the map id for this track:
try {
Track track = providerUtils.getTrack(trackId);
track.setMapId(mapId);
providerUtils.updateTrack(track);
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(MyTracksConstants.TAG, "Updating map id failed.", e);
}
}
onSendToGoogleMapsDone();
}
};
final SendToMyMaps sender = new SendToMyMaps(this, mapId, auth,
trackId, this /*progressIndicator*/, onCompletion);
HandlerThread handlerThread = new HandlerThread("SendToMyMaps");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(sender);
}
private void onSendToGoogleMapsDone() {
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
if (sendToGoogleDialog.getSendToFusionTables()) {
authenticateToFusionTables(null);
} else if (sendToGoogleDialog.getSendToDocs()) {
authenticateToGoogleDocs();
} else {
onSendToGoogleDone();
}
}
private void authenticateToFusionTables(Intent results) {
if (results == null) { results = new Intent(); }
setProgressValue(0);
setProgressMessage(R.string.progress_message_authenticating_fusiontables);
authenticate(results, MyTracksConstants.AUTHENTICATE_TO_FUSION_TABLES,
SendToFusionTables.SERVICE_ID);
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
}
private void sendToFusionTables(final long trackId) {
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
@Override
public void onSendCompleted(String tableId, boolean success,
int statusMessage) {
sendToFusionTablesMessage = getString(statusMessage);
sendToFusionTablesSuccess = success;
if (sendToFusionTablesSuccess) {
sendToFusionTablesTableId = tableId;
// Update the table id for this track:
try {
Track track = providerUtils.getTrack(trackId);
track.setTableId(tableId);
providerUtils.updateTrack(track);
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(MyTracksConstants.TAG, "Updating table id failed.", e);
}
}
onSendToFusionTablesDone();
}
};
sendToTrackId = trackId;
final SendToFusionTables sender = new SendToFusionTables(this, auth,
trackId, this/*progressIndicator*/, onCompletion);
HandlerThread handlerThread = new HandlerThread("SendToFusionTables");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(sender);
}
private void onSendToFusionTablesDone() {
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
if (sendToGoogleDialog.getSendToDocs()) {
authenticateToGoogleDocs();
} else {
onSendToGoogleDone();
}
}
private void authenticateToGoogleDocs() {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(new Intent(),
MyTracksConstants.AUTHENTICATE_TO_DOCLIST,
SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
// AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
}
private void authenticateToGoogleTrix() {
setProgressValue(30);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(new Intent(),
MyTracksConstants.AUTHENTICATE_TO_TRIX,
SendToDocs.GDATA_SERVICE_NAME_TRIX);
// AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
}
private void sendToGoogleDocs(final long trackId) {
Log.d(MyTracksConstants.TAG, "Sending to Docs....");
setProgressValue(50);
setProgressMessage(R.string.progress_message_sending_docs);
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST), trackId);
sendToTrackId = trackId;
Runnable onCompletion = new Runnable() {
public void run() {
setProgressValue(100);
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
sendToDocsMessage = sender.getStatusMessage();
sendToDocsSuccess = sender.wasSuccess();
onSendToGoogleDocsDone();
}
};
sender.setOnCompletion(onCompletion);
sender.run();
}
private void onSendToGoogleDocsDone() {
onSendToGoogleDone();
}
private void onSendToGoogleDone() {
SendToGoogleDialog sendToGoogleDialog = dialogManager.getSendToGoogleDialog();
final boolean sentToMyMaps = sendToGoogleDialog.getSendToMyMaps();
final boolean sentToFusionTables = sendToGoogleDialog.getSendToFusionTables();
dialogManager.dismissDialogSafely(DIALOG_PROGRESS);
runOnUiThread(new Runnable() {
public void run() {
if (shareRequested) {
Toast.makeText(MyTracks.this, getSendToGoogleResultMessage(), Toast.LENGTH_LONG)
.show();
if (shareLinkToMap(sentToMyMaps, sentToFusionTables)) {
return;
}
}
// If anything failed or sharing was not requested, show the dialog
dialogManager.showDialogSafely(
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
}
});
}
boolean shareLinkToMap(boolean sentToMyMaps, boolean sentToFusionTables) {
String url = null;
if (sentToMyMaps && sendToMyMapsSuccess) {
// Prefer a link to My Maps
url = MapsService.buildMapUrl(sendToMyMapsMapId);
} else if (sentToFusionTables && sendToFusionTablesSuccess) {
// Otherwise try using the link to fusion tables
url = getFusionTablesUrl(sendToTrackId);
}
if (url != null) {
shareLinkToMap(url);
return true;
}
return false;
}
protected String getFusionTablesUrl(long sendToTrackId2) {
Track track = providerUtils.getTrack(sendToTrackId);
return SendToFusionTables.getMapVisualizationUrl(track);
}
String getSendToGoogleResultMessage() {
StringBuilder message = new StringBuilder();
SendToGoogleDialog sendToGoogleDialog =
dialogManager.getSendToGoogleDialog();
if (sendToGoogleDialog.getSendToMyMaps()) {
message.append(sendToMyMapsMessage);
}
if (sendToGoogleDialog.getSendToFusionTables()) {
message.append(sendToFusionTablesMessage);
}
if (sendToGoogleDialog.getSendToDocs()) {
if (message.length() > 0) {
message.append(' ');
}
message.append(sendToDocsMessage);
}
if (sendToMyMapsSuccess && sendToFusionTablesSuccess && sendToDocsSuccess) {
message.append(' ');
message.append(getString(R.string.status_mymap_info));
}
return message.toString();
}
/**
* Writes the selected track id to the shared preferences.
* Executed on the UI thread.
@@ -1157,35 +1347,6 @@ public class MyTracks extends TabActivity implements OnTouchListener,
writer.writeTrackAsync();
}
/**
* Notifies that uploading to fusion tables is finished.
*/
private void handleFusionTablesFinish(long trackId) {
if (shareRequested && sendToFusionTablesSuccess) {
// Just share
Toast.makeText(this, getFusionTablesResultMessage(), Toast.LENGTH_LONG).show();
shareLinkToFusionTable(trackId);
} else {
dialogManager.showDialogSafely(DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT);
}
}
public String getFusionTablesResultMessage() {
StringBuilder message = new StringBuilder();
SendToGoogleDialog sendToGoogleDialog =
dialogManager.getSendToGoogleDialog();
if (sendToGoogleDialog.getSendToFusionTables()) {
message.append(sendToFusionTablesMessage);
}
if (sendToGoogleDialog.getSendToDocs()) {
if (message.length() > 0) {
message.append(' ');
}
message.append(sendToDocsMessage);
}
return message.toString();
}
public AccountChooser getAccountChooser() {
return accountChooser;
}
@@ -1202,6 +1363,10 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return dialogManager;
}
public String getSendToMyMapsMapId() {
return sendToMyMapsMapId;
}
public String getSendToFusionTablesTableId() {
return sendToFusionTablesTableId;
}
@@ -50,26 +50,26 @@ public abstract class MyTracksConstants {
public static final int FEATURE_DETAILS = 5;
public static final int START_RECORDING = 6;
public static final int STOP_RECORDING = 7;
public static final int AUTHENTICATE_TO_DOCLIST = 8;
public static final int AUTHENTICATE_TO_TRIX = 9;
public static final int SEND_TO_DOCS = 10;
public static final int DELETE_TRACK = 11;
public static final int SEND_TO_GOOGLE = 12;
public static final int SEND_TO_GOOGLE_DIALOG = 13;
public static final int SHARE_LINK = 14;
public static final int SHARE_GPX_FILE = 15;
public static final int SHARE_KML_FILE = 16;
public static final int SHARE_CSV_FILE = 17;
public static final int SHARE_TCX_FILE = 18;
public static final int EDIT_DETAILS = 19;
public static final int SAVE_GPX_FILE = 20;
public static final int SAVE_KML_FILE = 21;
public static final int SAVE_CSV_FILE = 22;
public static final int SAVE_TCX_FILE = 23;
public static final int CLEAR_MAP = 24;
public static final int SHOW_WAYPOINT = 25;
public static final int EDIT_WAYPOINT = 26;
public static final int WELCOME = 27;
public static final int AUTHENTICATE_TO_MY_MAPS = 8;
public static final int AUTHENTICATE_TO_FUSION_TABLES = 9;
public static final int AUTHENTICATE_TO_DOCLIST = 10;
public static final int AUTHENTICATE_TO_TRIX = 11;
public static final int DELETE_TRACK = 13;
public static final int SEND_TO_GOOGLE_DIALOG = 14;
public static final int SHARE_LINK = 15;
public static final int SHARE_GPX_FILE = 16;
public static final int SHARE_KML_FILE = 17;
public static final int SHARE_CSV_FILE = 18;
public static final int SHARE_TCX_FILE = 19;
public static final int EDIT_DETAILS = 20;
public static final int SAVE_GPX_FILE = 21;
public static final int SAVE_KML_FILE = 22;
public static final int SAVE_CSV_FILE = 23;
public static final int SAVE_TCX_FILE = 24;
public static final int CLEAR_MAP = 25;
public static final int SHOW_WAYPOINT = 26;
public static final int EDIT_WAYPOINT = 27;
public static final int WELCOME = 28;
/*
* Menu ids:
@@ -83,7 +83,7 @@ public class MyTracksList extends ListActivity
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
R.string.tracklist_share_track);
share.add(0, MyTracksConstants.MENU_SHARE_LINK, 0,
R.string.tracklist_share_linkfusion);
R.string.tracklist_share_link);
share.add(0, MyTracksConstants.MENU_SHARE_GPX_FILE, 0,
R.string.tracklist_share_gpx_file);
share.add(0, MyTracksConstants.MENU_SHARE_KML_FILE, 0,
@@ -178,6 +178,10 @@ public class MyTracksList extends ListActivity
new ExportAllTracks(this);
break;
}
case R.id.tracklist_btn_import_all: {
new ImportAllTracks(this);
break;
}
}
}
@@ -195,6 +199,7 @@ public class MyTracksList extends ListActivity
findViewById(R.id.tracklist_btn_delete_all).setOnClickListener(this);
findViewById(R.id.tracklist_btn_export_all).setOnClickListener(this);
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
SharedPreferences preferences =
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
@@ -201,7 +201,10 @@ public class MyTracksMap extends MapActivity
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
if (cursor != null && cursor.moveToFirst()) {
do {
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
Waypoint waypoint = providerUtils.createWaypoint(cursor);
if (MyTracksUtils.isValidLocation(waypoint.getLocation())) {
mapOverlay.addWaypoint(waypoint);
}
} while (cursor.moveToNext());
}
} catch (RuntimeException e) {
@@ -789,7 +792,7 @@ public class MyTracksMap extends MapActivity
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
R.string.tracklist_share_track);
share.add(0, MyTracksConstants.MENU_SHARE_LINK, 0,
R.string.tracklist_share_linkfusion);
R.string.tracklist_share_link);
share.add(0, MyTracksConstants.MENU_SHARE_GPX_FILE, 0,
R.string.tracklist_share_gpx_file);
share.add(0, MyTracksConstants.MENU_SHARE_KML_FILE, 0,
@@ -16,11 +16,14 @@
package com.google.android.apps.mytracks;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
import com.google.android.maps.mytracks.R;
@@ -35,6 +38,7 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.provider.Settings;
/**
@@ -103,6 +107,21 @@ public class MyTracksSettings extends PreferenceActivity {
});
updatePreferenceUnits(metricUnitsPreference.isChecked());
customizeSensorOptionsPreferences();
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
}
}
private void customizeSensorOptionsPreferences() {
ListPreference sensorTypePreference =
(ListPreference) findPreference(getString(R.string.sensor_type_key));
sensorTypePreference.setOnPreferenceChangeListener(
@@ -115,16 +134,37 @@ public class MyTracksSettings extends PreferenceActivity {
}
});
updateSensorSettings(sensorTypePreference.getValue());
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
if (!AntUtils.hasAntSupport(this)) {
// The sensor options screen has a few ANT-specific options which we
// need to remove. First, we need to remove the ANT sensor types.
// Second, we need to remove the ANT unpairing options.
Set<Integer> toRemove = new HashSet<Integer>();
String[] antValues = getResources().getStringArray(R.array.ant_sensor_type_values);
for (String antValue : antValues) {
toRemove.add(sensorTypePreference.findIndexOfValue(antValue));
}
CharSequence[] entries = sensorTypePreference.getEntries();
CharSequence[] entryValues = sensorTypePreference.getEntryValues();
CharSequence[] filteredEntries = new CharSequence[entries.length - toRemove.size()];
CharSequence[] filteredEntryValues = new CharSequence[filteredEntries.length];
for (int i = 0, last = 0; i < entries.length; i++) {
if (!toRemove.contains(i)) {
filteredEntries[last] = entries[i];
filteredEntryValues[last++] = entryValues[i];
}
}
sensorTypePreference.setEntries(filteredEntries);
sensorTypePreference.setEntryValues(filteredEntryValues);
PreferenceScreen sensorOptionsScreen =
(PreferenceScreen) findPreference(getString(R.string.sensor_options_key));
sensorOptionsScreen.removePreference(findPreference(getString(R.string.ant_options_key)));
}
}
@@ -217,7 +257,7 @@ public class MyTracksSettings extends PreferenceActivity {
final ListPreference splitFrequency =
(ListPreference) findPreference(
getString(R.string.split_frequency_key));
minRecordingDistance.setEntries(isMetric
? R.array.min_recording_distance_options
: R.array.min_recording_distance_options_ft);
@@ -29,6 +29,7 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.RadioGroup;
/**
* A dialog where the user can choose where to send the tracks to, i.e.
@@ -38,6 +39,10 @@ import android.widget.RadioButton;
*/
public class SendToGoogleDialog extends Dialog {
private RadioGroup groupMyMaps;
private RadioButton createNewMapRadioButton;
private RadioButton pickMapRadioButton;
private CheckBox sendToMyMapsCheckBox;
private CheckBox sendToFusionTablesCheckBox;
private CheckBox sendToDocsCheckBox;
private RadioButton sendStatsRadioButton;
@@ -72,19 +77,29 @@ public class SendToGoogleDialog extends Dialog {
OnCheckedChangeListener checkBoxListener = new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton button, boolean checked) {
sendButton.setEnabled(
sendToMyMapsCheckBox.isChecked() ||
sendToFusionTablesCheckBox.isChecked() ||
sendToDocsCheckBox.isChecked());
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
//groupDocs.setVisibility(
// sendToDocsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
}
};
sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
sendToMyMapsCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToFusionTablesCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
createNewMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
pickMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
sendStatsRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_send_stats);
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
@@ -93,12 +108,21 @@ public class SendToGoogleDialog extends Dialog {
SharedPreferences prefs =
getContext().getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
sendToFusionTablesCheckBox.setChecked(
sendToMyMapsCheckBox.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_to_my_maps_key), true));
sendToFusionTablesCheckBox.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_to_fusion_tables_key), true));
sendToDocsCheckBox.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_to_docs_key), true));
pickMapRadioButton.setChecked(
prefs.getBoolean(
getContext().getString(R.string.pick_existing_map_key), false));
createNewMapRadioButton.setChecked(
!prefs.getBoolean(
getContext().getString(R.string.pick_existing_map_key), false));
sendStatsAndPointsRadioButton.setChecked(
prefs.getBoolean(
getContext().getString(R.string.send_stats_and_points_key),
@@ -119,10 +143,16 @@ public class SendToGoogleDialog extends Dialog {
if (editor != null) {
editor.putBoolean(
getContext().getString(R.string.send_to_my_maps_key),
sendToMyMapsCheckBox.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_to_fusion_tables_key),
sendToFusionTablesCheckBox.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_to_docs_key),
sendToDocsCheckBox.isChecked());
editor.putBoolean(
getContext().getString(R.string.pick_existing_map_key),
pickMapRadioButton.isChecked());
editor.putBoolean(
getContext().getString(R.string.send_stats_and_points_key),
sendStatsAndPointsRadioButton.isChecked());
@@ -132,6 +162,10 @@ public class SendToGoogleDialog extends Dialog {
super.onStop();
}
public boolean getSendToMyMaps() {
return sendToMyMapsCheckBox.isChecked();
}
public boolean getSendToFusionTables() {
return sendToFusionTablesCheckBox.isChecked();
}
@@ -140,6 +174,10 @@ public class SendToGoogleDialog extends Dialog {
return sendToDocsCheckBox.isChecked();
}
public boolean getCreateNewMap() {
return createNewMapRadioButton.isChecked();
}
public boolean getSendStatsAndPoints() {
return sendStatsAndPointsRadioButton.isChecked();
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -371,8 +372,11 @@ public class MyTracksProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL " + url);
}
Log.i(MyTracksConstants.TAG,
"Build query: " + qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null));
if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) {
Log.i(MyTracksConstants.TAG,
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
null, null, sortOrder, null));
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
sortOrder);
c.setNotificationUri(getContext().getContentResolver(), url);
@@ -23,6 +23,7 @@ import android.location.Location;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
@@ -44,9 +45,13 @@ import java.util.TimeZone;
* @author Rodrigo Damazio
*/
public class CsvTrackWriter implements TrackFormatWriter {
static final NumberFormat SHORT_FORMAT = NumberFormat.getInstance();
static final SimpleDateFormat TIMESTAMP_FORMAT =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
SHORT_FORMAT.setMaximumFractionDigits(4);
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -94,8 +99,8 @@ public class CsvTrackWriter implements TrackFormatWriter {
Double.toString(location.getLongitude()),
Double.toString(location.getAltitude()),
Double.toString(location.getBearing()),
Double.toString(location.getAccuracy()),
Double.toString(location.getSpeed()),
SHORT_FORMAT.format(location.getAccuracy()),
SHORT_FORMAT.format(location.getSpeed()),
null, null,
Integer.toString(segmentIdx));
}
@@ -110,8 +115,8 @@ public class CsvTrackWriter implements TrackFormatWriter {
Double.toString(location.getLongitude()),
Double.toString(location.getAltitude()),
Double.toString(location.getBearing()),
Double.toString(location.getAccuracy()),
Double.toString(location.getSpeed()),
SHORT_FORMAT.format(location.getAccuracy()),
SHORT_FORMAT.format(location.getSpeed()),
waypoint.getName(),
waypoint.getDescription(),
null);
@@ -229,7 +229,10 @@ public class GpxImporter extends DefaultHandler {
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
if (!isInTrackElement) { return; }
if (!isInTrackElement) {
content = null;
return;
}
// process these elements only as sub-elements of track
if (localName.equals(TAG_TRACK_POINT)) {
@@ -360,6 +363,10 @@ public class GpxImporter extends DefaultHandler {
*/
private void onTrackPointElementEnd() throws SAXException {
if (MyTracksUtils.isValidLocation(location)) {
if (statsBuilder == null) {
// first point did not have a time, start stats builder without it
statsBuilder = new TripStatisticsBuilder(0);
}
statsBuilder.addLocation(location, location.getTime());
// insert in db
@@ -455,18 +462,18 @@ public class GpxImporter extends DefaultHandler {
// check for negative time change
if (timeDifference < 0) {
String msg = createErrorMessage("Found negative time change.");
throw new SAXException(msg);
}
Log.w(MyTracksConstants.TAG, "Found negative time change.");
} else {
// We don't have a speed and bearing in GPX, make something up from
// the last two points.
// TODO GPS points tend to have some inherent imprecision,
// speed and bearing will likely be off, so the statistics for things like
// max speed will also be off.
float speed = location.distanceTo(lastLocation) * 1000.0f / timeDifference;
location.setSpeed(speed);
location.setBearing(lastSegmentLocation.bearingTo(location));
// We don't have a speed and bearing in GPX, make something up from
// the last two points.
// TODO GPS points tend to have some inherent imprecision,
// speed and bearing will likely be off, so the statistics for things like
// max speed will also be off.
float speed = location.distanceTo(lastLocation) * 1000.0f / timeDifference;
location.setSpeed(speed);
location.setBearing(lastSegmentLocation.bearingTo(location));
}
}
// Fill in the time
@@ -26,6 +26,7 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.TimeZone;
@@ -36,15 +37,23 @@ import java.util.TimeZone;
*/
public class GpxTrackWriter implements TrackFormatWriter {
static final SimpleDateFormat TIMESTAMP_FORMAT =
private static final int LATLONG_FORMAT = Location.FORMAT_DEGREES;
private static final DecimalFormat ELEVATION_FORMATTER =
new DecimalFormat("#.#");
private static final SimpleDateFormat TIMESTAMP_FORMATTER =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
TIMESTAMP_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private PrintWriter pw = null;
private Track track;
private String formatLocation(Location l) {
return "lat=\"" + Location.convert(l.getLatitude(), LATLONG_FORMAT)
+ "\" lon=\"" + Location.convert(l.getLongitude(), LATLONG_FORMAT) + "\"";
}
@Override
public void prepare(Track track, OutputStream out) {
this.track = track;
@@ -63,14 +72,14 @@ public class GpxTrackWriter implements TrackFormatWriter {
Charset.defaultCharset().name());
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
pw.println("<gpx");
pw.println(" version=\"1.0\"");
pw.println(" version=\"1.1\"");
pw.println(" creator=\"My Tracks for the G1 running Android\"");
pw.println(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
pw.println(" xmlns=\"http://www.topografix.com/GPX/1/0\"");
pw.println(" xmlns=\"http://www.topografix.com/GPX/1/1\"");
pw.print(" xmlns:topografix=\"http://www.topografix.com/GPX/Private/"
+ "TopoGrafix/0/1\"");
pw.print(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 ");
pw.print("http://www.topografix.com/GPX/1/0/gpx.xsd ");
pw.print(" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 ");
pw.print("http://www.topografix.com/GPX/1/1/gpx.xsd ");
pw.print("http://www.topografix.com/GPX/Private/TopoGrafix/0/1 ");
pw.println("http://www.topografix.com/GPX/Private/TopoGrafix/0/1/"
+ "topografix.xsd\">");
@@ -94,7 +103,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
pw.println("<desc>" + StringUtils.stringAsCData(track.getDescription())
+ "</desc>");
pw.println("<number>" + track.getId() + "</number>");
pw.println("<topografix:color>c0c0c0</topografix:color>");
pw.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
}
}
@@ -118,11 +127,10 @@ public class GpxTrackWriter implements TrackFormatWriter {
@Override
public void writeLocation(Location l) {
if (pw != null) {
pw.println("<trkpt lat=\"" + l.getLatitude() + "\" lon=\""
+ l.getLongitude() + "\">");
pw.println("<trkpt " + formatLocation(l) + ">");
Date d = new Date(l.getTime());
pw.println("<ele>" + l.getAltitude() + "</ele>");
pw.println("<time>" + TIMESTAMP_FORMAT.format(d) + "</time>");
pw.println("<ele>" + ELEVATION_FORMATTER.format(l.getAltitude()) + "</ele>");
pw.println("<time>" + TIMESTAMP_FORMATTER.format(d) + "</time>");
pw.println("</trkpt>");
}
}
@@ -138,17 +146,15 @@ public class GpxTrackWriter implements TrackFormatWriter {
@Override
public void writeWaypoint(Waypoint waypoint) {
if (pw != null) {
// TODO: The gpx spec says waypoints should come *before* tracks
Location l = waypoint.getLocation();
if (l != null) {
pw.println("<wpt lat=\"" + l.getLatitude() + "\" lon=\""
+ l.getLongitude() + "\">");
pw.println("<wpt " + formatLocation(l) + ">");
pw.println("<ele>" + ELEVATION_FORMATTER.format(l.getAltitude()) + "</ele>");
pw.println("<time>" + TIMESTAMP_FORMATTER.format(l.getTime()) + "</time>");
pw.println("<name>" + StringUtils.stringAsCData(waypoint.getName())
+ "</name>");
pw.println("<desc>"
+ StringUtils.stringAsCData(waypoint.getDescription()) + "</desc>");
pw.println("<time>" + TIMESTAMP_FORMAT.format(l.getTime()) + "</time>");
pw.println("<ele>" + l.getAltitude() + "</ele>");
pw.println("</wpt>");
}
}
@@ -0,0 +1,68 @@
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.mymaps.MapsService;
import com.google.android.apps.mytracks.io.mymaps.MapsStringsProvider;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade.AuthenticationRefresher;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Context;
/**
* Factory to easily instantiate a {@link MapsFacade}.
*
* @author Rodrigo Damazio
*/
public class MyMapsFactory {
/**
* Simple provider for reading strings from resources.
*/
private static class MyMapsStringProvider implements MapsStringsProvider {
private final Context context;
public MyMapsStringProvider(Context context) {
this.context = context;
}
@Override
public String getNewMapDescription() {
return context.getString(R.string.new_map_description);
}
@Override
public String getStart() {
return context.getString(R.string.start);
}
@Override
public String getEnd() {
return context.getString(R.string.end);
}
}
/**
* Creates a new Maps fa�ade interface instance.
*
* @param context current context
* @param auth a valid authentication manager
* @return the fa�ade
*/
public static MapsFacade newMapsClient(Activity context, final AuthManager auth) {
MapsFacade client = MapsService.newClient(context,
GDataClientFactory.getGDataClient(context),
new MyMapsStringProvider(context),
auth.getAuthToken());
client.setAuthenticationRefresher(new AuthenticationRefresher() {
@Override
public void invalidateAndRefresh(Runnable done) {
auth.invalidateAndRefresh(done);
}
});
return client;
}
private MyMapsFactory() {}
}
@@ -28,7 +28,7 @@ import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.docs.DocumentsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient;
import com.google.wireless.gdata.maps.xml.XmlMapsGDataParserFactory;
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
import com.google.wireless.gdata.client.GDataServiceClient;
import android.app.Activity;
@@ -99,6 +99,7 @@ public class SendToDocs {
}
private void doUpload() {
// TODO
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
success = false;
@@ -173,7 +174,7 @@ public class SendToDocs {
try {
androidClient = GDataClientFactory.getGDataClient(activity);
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
trixWrapper.setClient(gdataClient);
Log.d(MyTracksConstants.TAG,
"GData connection prepared: " + this.docListAuth);
@@ -184,7 +185,7 @@ public class SendToDocs {
}
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
new XmlDocsGDataParserFactory(new AndroidXmlParserFactory()));
docListWrapper.setClient(docsGdataClient);
// First try to find the spreadsheet:
@@ -51,6 +51,7 @@ import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
/**
@@ -72,7 +73,7 @@ public class SendToFusionTables implements Runnable {
public static final String SERVICE_ID = "fusiontables";
/** The path for viewing a map visualization of a table. */
private static final String FUSIONTABLES_MAP =
private static final String FUSIONTABLES_MAP =
"http://www.google.com/fusiontables/embedviz?" +
"viz=MAP&q=select+col0,+col1,+col2,+col3+from+%s+&h=false&" +
"lat=%f&lng=%f&z=%d&t=1&l=col2";
@@ -84,10 +85,10 @@ public class SendToFusionTables implements Runnable {
private static final int MAX_POINTS_PER_UPLOAD = 2048;
private static final String GDATA_VERSION = "2";
// This class reports upload status to the user as a completion percentage
// using a progress bar. Progress is defined as follows:
//
//
// 0% Getting track metadata
// 5% Creating Fusion Table (GData to FT server)
// 10%-90% Uploading the track data to Fusion Tables
@@ -160,7 +161,12 @@ public class SendToFusionTables implements Runnable {
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
double lonE6 = stats.getLeft() + (stats.getRight() - stats.getLeft()) / 2;
int z = 15;
return String.format(FUSIONTABLES_MAP, track.getTableId(), latE6 / 1.E6, lonE6 / 1.E6, z);
// We explicitly format with Locale.US because we need the latitude and
// longitude to be formatted in a locale-independent manner. Specifically,
// we need the decimal separator to be a period rather than a comma.
return String.format(Locale.US, FUSIONTABLES_MAP, track.getTableId(),
latE6 / 1.E6, lonE6 / 1.E6, z);
}
private void doUpload() {
@@ -189,14 +195,14 @@ public class SendToFusionTables implements Runnable {
if (!uploadAllTrackPoints(track, originalDescription)) {
return;
}
progressIndicator.setProgressValue(PROGRESS_UPLOAD_WAYPOINTS);
// Upload all the waypoints.
if (!uploadWaypoints(track)) {
return;
}
statusMessageId = R.string.status_new_fusiontable_has_been_created;
Log.d(MyTracksConstants.TAG, "SendToFusionTables: Done: " + success);
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
@@ -253,7 +259,7 @@ public class SendToFusionTables implements Runnable {
builder.append(')');
return builder.toString();
}
private static String sqlEscape(String value) {
return value.replaceAll("'", "''");
}
@@ -632,8 +638,8 @@ public class SendToFusionTables implements Runnable {
double totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
/ (totalLocations * 3);
double scaledPercentage = totalPercentage
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
progressIndicator.setProgressValue((int) scaledPercentage);
@@ -670,7 +676,7 @@ public class SendToFusionTables implements Runnable {
try {
response = request.execute();
} catch (HttpResponseException e) {
throw new GDataWrapper.HttpException(e.response.statusCode, e.getMessage());
throw new GDataWrapper.HttpException(e.response.statusCode, e.response.statusMessage);
}
boolean success = response.isSuccessStatusCode;
if (success) {
@@ -0,0 +1,458 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade.WaypointData;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.MyTracksUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* A helper class used to transmit tracks to Google MyMaps.
* A new instance should be used for each upload.
*
* IMPORTANT: while this code is Apache-licensed, please notice that usage of
* the Google Maps servers through this API is only allowed for the My Tracks
* application. Other applications looking to upload maps data should look
* into using the Fusion Tables API.
*
* @author Leif Hendrik Wilden
*/
public class SendToMyMaps implements Runnable {
public static final String NEW_MAP_ID = "new";
private static final int MAX_POINTS_PER_UPLOAD = 2048;
private final Activity context;
private final AuthManager auth;
private final long trackId;
private final ProgressIndicator progressIndicator;
private final OnSendCompletedListener onCompletion;
private final StringUtils stringUtils;
private final MyTracksProviderUtils providerUtils;
private String mapId;
private MapsFacade mapsClient;
// Progress status
private int totalLocationsRead;
private int totalLocationsPrepared;
private int totalLocationsUploaded;
private int totalLocations;
private int totalSegmentsUploaded;
public interface OnSendCompletedListener {
void onSendCompleted(String mapId, boolean success, int statusMessage);
}
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 {
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>");
mapsClient = MyMapsFactory.newMapsClient(context, auth);
// Create a new map if necessary:
boolean isNewMap = mapId.equals(NEW_MAP_ID);
if (isNewMap) {
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);
}
progressIndicator.setProgressMessage(
R.string.progress_message_creating_map);
StringBuilder mapIdBuilder = new StringBuilder();
success = mapsClient.createNewMap(
track.getName(), track.getCategory(), originalDescription, mapPublic, mapIdBuilder);
mapId = mapIdBuilder.toString();
}
// Upload all of the segments of the track plus start/end markers
if (success) {
success = uploadAllTrackPoints(track, originalDescription);
}
// Put waypoints.
if (success) {
Cursor c = providerUtils.getWaypointsCursor(
track.getId(), 0,
MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS);
if (c != null) {
try {
if (c.getCount() > 1 && c.moveToFirst()) {
// This will skip the 1st waypoint (it carries the stats for the
// last segment).
ArrayList<WaypointData> waypoints = new ArrayList<WaypointData>(c.getCount() - 1);
while (c.moveToNext()) {
Waypoint wpt = providerUtils.createWaypoint(c);
waypoints.add(new WaypointData(
wpt.getName(), wpt.getDescription(), wpt.getIcon(), wpt.getLocation()));
}
success = mapsClient.uploadWaypoints(mapId, waypoints);
}
} finally {
c.close();
}
} else {
success = false;
}
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);
} finally {
if (mapsClient != null) {
mapsClient.cleanUp();
}
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
context.runOnUiThread(new Runnable() {
public void run() {
if (onCompletion != null) {
onCompletion.onSendCompleted(
mapId, finalSuccess, finalStatusMessageId);
}
}
});
}
}
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:
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), 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 mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
lastLocation, false);
}
return true;
} finally {
locationsCursor.close();
}
}
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 (!mapsClient.uploadTrackPoints(mapId, splitTrack.getName(), splitTrack.getLocations())) {
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.
*
* @param track the original track with meta data
* @param buffer a buffer of locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
private ArrayList<Track> prepareLocations(
Track track, Iterable<Location> locations) {
ArrayList<Track> splitTracks = new ArrayList<Track>();
// Create segments from each full track:
Track segment = new Track();
TripStatistics segmentStats = segment.getStatistics();
TripStatistics trackStats = track.getStatistics();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
segmentStats.setStartTime(trackStats.getStartTime());
segmentStats.setStopTime(trackStats.getStopTime());
boolean startNewTrackSegment = false;
for (Location loc : locations) {
if (totalLocationsPrepared % 100 == 0) {
updateProgress();
}
if (loc.getLatitude() > 90) {
startNewTrackSegment = true;
}
if (startNewTrackSegment) {
// Close up the last segment.
prepareTrackSegment(segment, splitTracks);
Log.d(MyTracksConstants.TAG,
"MyTracksSendToMyMaps: Starting new track segment...");
startNewTrackSegment = false;
segment = new Track();
segment.setId(track.getId());
segment.setName(track.getName());
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
}
if (loc.getLatitude() <= 90) {
segment.addLocation(loc);
if (segmentStats.getStartTime() < 0) {
segmentStats.setStartTime(loc.getTime());
}
}
totalLocationsPrepared++;
}
prepareTrackSegment(segment, splitTracks);
return splitTracks;
}
/**
* Prepares a track segment for sending to google maps.
* The main steps are:
* - correcting end time
* - decimating locations
* - splitting into smaller tracks.
*
* The final track pieces will be put in the array list splitTracks.
*
* @param segment the original segment of the track
* @param splitTracks an array of smaller track segments
*/
private void prepareTrackSegment(
Track segment, ArrayList<Track> splitTracks) {
TripStatistics segmentStats = segment.getStatistics();
if (segmentStats.getStopTime() < 0
&& segment.getLocations().size() > 0) {
segmentStats.setStopTime(segment.getLocations().size() - 1);
}
/*
* Decimate to 2 meter precision. Mapshop doesn't like too many
* points:
*/
MyTracksUtils.decimate(segment, 2.0);
/* It the track still has > 500 points, split it in pieces: */
if (segment.getLocations().size() > 500) {
splitTracks.addAll(MyTracksUtils.split(segment, 500));
} else if (segment.getLocations().size() >= 2) {
splitTracks.add(segment);
}
}
/**
* 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);
}
}
@@ -255,9 +255,8 @@ public class TrackWriter {
void writeDocument() {
Log.d(MyTracksConstants.TAG, "Started writing track.");
writer.writeHeader();
// TODO: Fix ordering (in GPX waypoints should come first)
writeLocations();
writeWaypoints(track.getId());
writeLocations();
writer.writeFooter();
writer.close();
success = true;
@@ -187,6 +187,7 @@ public class AndroidGDataClient implements GDataClient {
while (redirectsLeft > 0) {
HttpUriRequest request = creator.createRequest(uri);
request.addHeader("User-Agent", "Android-GData");
request.addHeader("Accept-Encoding", "gzip");
// only add the auth token if not null (to allow for GData feeds that do
@@ -210,9 +210,10 @@ public class GDataWrapper<C> {
errorType = ERROR_AUTH;
errorMessage = e.getMessage();
} catch (HttpException e) {
Log.e(MyTracksConstants.TAG, "HttpException", e);
Log.e(MyTracksConstants.TAG,
"HttpException, code " + e.getStatusCode() + " message " + e.getMessage(), e);
errorMessage = e.getMessage();
if (errorMessage.contains("401")) {
if (e.getStatusCode() == 401) {
errorType = ERROR_AUTH;
} else {
errorType = ERROR_CONNECTION;
@@ -224,7 +225,7 @@ public class GDataWrapper<C> {
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Exception", e);
errorMessage = e.getMessage();
if (errorMessage.contains("503")) {
if (errorMessage != null && errorMessage.contains("503")) {
errorType = ERROR_INTERNAL;
} else {
errorType = ERROR_CONNECTION;
@@ -0,0 +1,78 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services;
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
import android.content.Context;
import android.media.AudioManager;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import java.util.HashMap;
/**
* This class will periodically announce the user's trip statistics for Froyo and future handsets.
* This class will request and release audio focus.
*
* @author Sandor Dornbush
*/
public class FroyoStatusAnnouncerTask extends StatusAnnouncerTask {
private final static HashMap<String, String> SPEECH_PARAMS = new HashMap<String, String>();
static {
SPEECH_PARAMS.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "not_used");
}
private final AudioManager audioManager;
private final OnUtteranceCompletedListener utteranceListener =
new OnUtteranceCompletedListener() {
@Override
public void onUtteranceCompleted(String utteranceId) {
if (audioManager != null) {
Log.d(TAG, "FroyoStatusAnnouncerTask: Abandoning audio focus.");
audioManager.abandonAudioFocus(null);
}
}
};
public FroyoStatusAnnouncerTask(Context context) {
super(context);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}
@Override
protected void onTtsInit(int status) {
super.onTtsInit(status);
if (status == TextToSpeech.SUCCESS) {
tts.setOnUtteranceCompletedListener(utteranceListener);
}
}
@Override
protected void speakAnnouncment(String announcement) {
int result = audioManager.requestAudioFocus(null,
TextToSpeech.Engine.DEFAULT_STREAM,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.w(TAG, "FroyoStatusAnnouncerTask: Request audio focus failed.");
}
// We don't care about the utterance id.
// It is supplied here to force onUtteranceCompleted to be called.
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, SPEECH_PARAMS);
}
}
@@ -41,7 +41,11 @@ public class StatusAnnouncerFactory {
*/
public PeriodicTask create(Context context) {
if (hasTts) {
return new StatusAnnouncerTask(context);
if (ApiFeatures.getInstance().isAudioFocusSupported()) {
return new FroyoStatusAnnouncerTask(context);
} else {
return new StatusAnnouncerTask(context);
}
} else {
return null;
}
@@ -59,7 +59,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
/**
* The interface to the text to speech engine.
*/
private TextToSpeech tts;
protected TextToSpeech tts;
/**
* The response received from the TTS engine after initialization.
@@ -98,7 +98,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
/**
* Called when the TTS engine is initialized.
*/
private void onTtsInit(int status) {
protected void onTtsInit(int status) {
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
this.ready = status == TextToSpeech.SUCCESS;
@@ -146,6 +146,10 @@ public class StatusAnnouncerTask implements PeriodicTask {
String announcement = getAnnouncement(service.getTripStatistics());
Log.d(MyTracksConstants.TAG, "Announcement: " + announcement);
speakAnnouncment(announcement);
}
protected void speakAnnouncment(String announcement) {
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
}
@@ -62,6 +62,8 @@ import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* A background service that registers a location listener and records track
@@ -187,6 +189,8 @@ public class TrackRecordingService extends Service implements LocationListener {
*/
private int announcementFrequency = -1;
private ExecutorService executerServce;
/*
* Utility functions
*/
@@ -420,6 +424,7 @@ public class TrackRecordingService extends Service implements LocationListener {
TripStatistics stats = track.getStatistics();
statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
statsBuilder.setMinRecordingDistance(minRecordingDistance);
setUpAnnouncer();
splitManager.restore();
@@ -436,6 +441,7 @@ public class TrackRecordingService extends Service implements LocationListener {
waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
currentWaypointId = -1;
}
waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
Cursor cursor = null;
try {
@@ -475,9 +481,18 @@ public class TrackRecordingService extends Service implements LocationListener {
/*
* Location listener implementation: =================================
*/
@Override
public void onLocationChanged(Location location) {
public void onLocationChanged(final Location location) {
this.executerServce.submit(
new Runnable() {
@Override
public void run() {
onLocationChangedAsync(location);
}
});
}
private void onLocationChangedAsync(Location location) {
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onLocationChanged");
try {
@@ -513,6 +528,7 @@ public class TrackRecordingService extends Service implements LocationListener {
// Update the idle time if needed.
locationListenerPolicy.updateIdleTime(statsBuilder.getIdleTime());
addLocationToStats(location);
if (currentRecordingInterval !=
locationListenerPolicy.getDesiredPollingInterval()) {
registerLocationListener();
@@ -538,8 +554,6 @@ public class TrackRecordingService extends Service implements LocationListener {
// two and ignore the rest. This code will only have an effect if the
// maxRecordingDistance = 0
if (distanceToLast == 0 && !hasSensorData) {
addLocationToStats(location);
if (isMoving) {
Log.d(MyTracksConstants.TAG, "Found two identical locations.");
isMoving = false;
@@ -561,7 +575,6 @@ public class TrackRecordingService extends Service implements LocationListener {
}
} else if (distanceToLastRecorded > minRecordingDistance
|| hasSensorData) {
addLocationToStats(location);
if (lastLocation != null && !isMoving) {
// Last location was the last stationary location. Need to go back and
// add it.
@@ -675,6 +688,7 @@ public class TrackRecordingService extends Service implements LocationListener {
prefManager.setRecordingTrack(recordingTrackId = -1);
}
showNotification();
executerServce = Executors.newSingleThreadExecutor();
}
/**
@@ -883,7 +897,14 @@ public class TrackRecordingService extends Service implements LocationListener {
}
wpt.setTrackId(recordingTrackId);
wpt.setLength(length);
if (lastValidLocation != null) {
if (lastValidLocation == null) {
// A null location is ok, and expected on track start.
// Make it an impossible location.
Location l = new Location("");
l.setLatitude(100);
l.setLongitude(200);
wpt.setLocation(l);
} else {
// A null location is ok, and expected on track start.
wpt.setLocation(lastLocation);
wpt.setDuration(lastLocation.getTime()
@@ -1076,10 +1097,12 @@ public class TrackRecordingService extends Service implements LocationListener {
recordingTrackId, startTime));
isRecording = true;
isMoving = true;
providerUtils.updateTrack(track);
statsBuilder = new TripStatisticsBuilder(startTime);
statsBuilder.setMinRecordingDistance(minRecordingDistance);
waypointStatsBuilder = new TripStatisticsBuilder(startTime);
waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
currentWaypointId = insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
setUpAnnouncer();
length = 0;
@@ -1197,6 +1220,10 @@ public class TrackRecordingService extends Service implements LocationListener {
public void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
if (statsBuilder != null && waypointStatsBuilder != null) {
statsBuilder.setMinRecordingDistance(minRecordingDistance);
waypointStatsBuilder.setMinRecordingDistance(minRecordingDistance);
}
}
public int getMinRequiredAccuracy() {
@@ -1,12 +1,12 @@
/*
* 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
@@ -18,10 +18,14 @@ package com.google.android.apps.mytracks.services.sensors;
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.sensors.ant.AntChannelIdMessage;
import com.google.android.apps.mytracks.services.sensors.ant.AntChannelResponseMessage;
import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
@@ -49,62 +53,70 @@ public class AntDirectSensorManager extends AntSensorManager {
public AntDirectSensorManager(Context context) {
super(context);
deviceNumberHRM = WILDCARD;
// First read the the device id that we will be pairing with.
SharedPreferences prefs = context.getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
if (prefs != null) {
deviceNumberHRM =
deviceNumberHRM =
(short) prefs.getInt(context.getString(R.string.ant_heart_rate_sensor_id_key), 0);
}
Log.i(TAG, "Pairing with heart rate monitor: " + deviceNumberHRM);
Log.i(TAG, "Will pair with heart rate monitor: " + deviceNumberHRM);
}
@Override
public void handleMessage(byte[] antMessage) {
int channel =
antMessage[AntMesg.MESG_DATA_OFFSET] & AntDefine.CHANNEL_NUMBER_MASK;
protected boolean handleMessage(byte messageId, byte[] messageData) {
if (super.handleMessage(messageId, messageData)) {
return true;
}
int channel = messageData[0] & AntDefine.CHANNEL_NUMBER_MASK;
switch (channel) {
case HRM_CHANNEL:
antDecodeHRM(antMessage);
antDecodeHRM(messageId, messageData);
break;
default:
Log.d(TAG, "Unhandled message: " + channel);
}
return true;
}
/**
* Decode an ant heart rate monitor message.
* @param antMessage The byte array received from the heart rate monitor.
* @param messageData The byte array received from the heart rate monitor.
*/
private void antDecodeHRM(byte[] antMessage) {
// message ID == broadcast
switch (antMessage[AntMesg.MESG_ID_OFFSET]) {
private void antDecodeHRM(int messageId, byte[] messageData) {
switch (messageId) {
case AntMesg.MESG_BROADCAST_DATA_ID:
handleBroadcastData(antMessage);
handleBroadcastData(messageData);
break;
case AntMesg.MESG_RESPONSE_EVENT_ID:
handleMessageResponse(antMessage);
handleMessageResponse(messageData);
break;
case AntMesg.MESG_CHANNEL_ID_ID:
handleChannelId(antMessage);
handleChannelId(messageData);
break;
default:
Log.e(TAG, "Unexpected message id: " + antMessage[3]);
Log.e(TAG, "Unexpected message id: " + messageId);
}
}
private void handleBroadcastData(byte[] antMessage) {
if (deviceNumberHRM == WILDCARD) {
getAntReceiver().ANTRequestMessage(HRM_CHANNEL,
AntMesg.MESG_CHANNEL_ID_ID);
try {
getAntReceiver().ANTRequestMessage(HRM_CHANNEL,
AntMesg.MESG_CHANNEL_ID_ID);
} catch (AntInterfaceException e) {
Log.e(TAG, "ANT error handling broadcast data", e);
}
Log.d(TAG, "Requesting channel id id.");
}
setSensorState(Sensor.SensorState.CONNECTED);
int bpm = (int) antMessage[10] & 0xFF;
int bpm = (int) antMessage[8] & 0xFF;
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(bpm)
.setState(Sensor.SensorState.SENDING);
@@ -115,11 +127,9 @@ public class AntDirectSensorManager extends AntSensorManager {
.build();
}
void handleChannelId(byte[] antMessage) {
// Store the device id.
deviceNumberHRM =
(short) (((int) antMessage[3] & 0xFF |
((int) (antMessage[4] & 0xFF) << 8)) & 0xFFFF);
void handleChannelId(byte[] rawMessage) {
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
deviceNumberHRM = message.getDeviceNumber();
Log.i(TAG, "Found device id: " + deviceNumberHRM);
SharedPreferences prefs = context.getSharedPreferences(
@@ -129,26 +139,31 @@ public class AntDirectSensorManager extends AntSensorManager {
editor.commit();
}
private void handleMessageResponse(byte[] antMessage) {
if (antMessage[3] == AntMesg.MESG_EVENT_ID &&
antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel((byte) 0);
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
private void handleMessageResponse(byte[] rawMessage) {
AntChannelResponseMessage message = new AntChannelResponseMessage(rawMessage);
switch (message.getMessageId()) {
case AntMesg.MESG_EVENT_ID:
if (message.getMessageCode() == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(TAG, "Search timed out. Unassigning channel.");
try {
getAntReceiver().ANTUnassignChannel((byte) 0);
} catch (AntInterfaceException e) {
Log.e(TAG, "ANT error unassigning channel", e);
}
setSensorState(Sensor.SensorState.DISCONNECTED);
}
break;
case AntMesg.MESG_UNASSIGN_CHANNEL_ID:
setSensorState(Sensor.SensorState.DISCONNECTED);
Log.i(TAG, "Disconnected from the sensor: " + getSensorState());
break;
}
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
super.setupChannel();
antChannelSetup(NETWORK_NUMBER,
@Override protected void setupAntSensorChannels() {
setupAntSensorChannel(NETWORK_NUMBER,
HRM_CHANNEL,
deviceNumberHRM,
HEART_RATE_DEVICE_TYPE,
@@ -156,7 +171,6 @@ public class AntDirectSensorManager extends AntSensorManager {
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
setSensorState(Sensor.SensorState.CONNECTING);
}
public short getDeviceNumberHRM() {
@@ -1,12 +1,12 @@
/*
* 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
@@ -22,6 +22,8 @@ import com.google.android.maps.mytracks.R;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
@@ -104,8 +106,12 @@ public class AntSRMSensorManager extends AntSensorManager {
private void handleBroadcastData(byte[] antMessage) {
if (deviceId == WILDCARD) {
getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID);
Log.d(MyTracksConstants.TAG, "Requesting channel id id.");
try {
Log.d(MyTracksConstants.TAG, "Requesting channel id id.");
getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID);
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "Failed to request channel id id", e);
}
}
setSensorState(Sensor.SensorState.CONNECTED);
switch(antMessage[MESSAGE_TYPE_INDEX]) {
@@ -138,7 +144,11 @@ public class AntSRMSensorManager extends AntSensorManager {
&& antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) {
// Search timeout
Log.w(MyTracksConstants.TAG, "Search timed out. Unassigning channel.");
getAntReceiver().ANTUnassignChannel(channel);
try {
getAntReceiver().ANTUnassignChannel(channel);
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "Failed to unassign ANT channel", e);
}
setSensorState(Sensor.SensorState.DISCONNECTED);
} else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) {
setSensorState(Sensor.SensorState.DISCONNECTED);
@@ -207,12 +217,10 @@ public class AntSRMSensorManager extends AntSensorManager {
sensorData = builder.build();
}
/**
* Open a channel to the SRM head unit.
*/
@Override
public void setupChannel() {
antChannelSetup(NETWORK_NUMBER,
protected void setupAntSensorChannels() {
lastMessageId = 0;
setupAntSensorChannel(NETWORK_NUMBER,
channel,
deviceId,
DEVICE_TYPE,
@@ -220,7 +228,5 @@ public class AntSRMSensorManager extends AntSensorManager {
CHANNEL_PERIOD,
RF_FREQUENCY,
(byte) 0);
lastMessageId = 0;
setSensorState(Sensor.SensorState.CONNECTING);
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -16,37 +16,56 @@
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.ant.AntStartupMessage;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntInterface;
import com.dsi.ant.AntInterfaceIntent;
import com.dsi.ant.AntMesg;
import com.dsi.ant.exception.AntInterfaceException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* An abstract class for ANT+ based sensors.
* This is the common superclass for ANT-based sensors. It handles tasks which
* apply to the ANT framework as a whole, such as framework initialization and
* destruction. Subclasses are expected to handle the initialization and
* management of individual sensors.
*
* Implementation:
*
* The initialization process is somewhat complicated. This is due in part to
* the asynchronous nature of ANT Radio Service initialization, and in part due
* to an apparent bug in that service. The following is an overview of the
* initialization process.
*
* Initialization begins in {@link #setupChannel}, which is invoked by the
* {@link SensorManager} when track recording begins. {@link #setupChannel}
* asks the ANT Radio Service (via {@link AntInterface}) to start, using a
* {@link AntInterface.ServiceListener} to indicate when the service has
* connected. {@link #serviceConnected} claims and enables the Radio Service,
* and then resets it to a known state for our use. Completion of reset is
* indicated by receipt of a startup message (see {@link AntStartupMessage}).
* Once we've received that message, the ANT service is ready for use, and we
* can start sensor-specific initialization using
* {@link #setupAntSensorChannels}. The initialization of each sensor will
* usually result in a call to {@link #setupAntSensorChannel}.
*
* @author Sandor Dornbush
*/
public abstract class AntSensorManager extends SensorManager {
// ANT Service
public static final int EVENT_ANT_ENABLED = 1;
public static final int EVENT_ANT_DISABLED = 2;
// Pairing
protected static final short WILDCARD = 0;
private AntInterface antReceiver;
private boolean status;
// Flag to know if the ANT App was interrupted
// TODO: This code path is not used but probably should be.
private boolean antInterrupted;
@@ -57,40 +76,29 @@ public abstract class AntSensorManager extends SensorManager {
protected SensorDataSet sensorData;
protected Context context;
private static final boolean DEBUGGING = false;
/**
* Receives all of the ANT intents and dispatches to the proper handler.
*/
private final BroadcastReceiver receiver = new BroadcastReceiver() {
/** Receives and logs all status ANT intents. */
private final BroadcastReceiver statusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String antAction = intent.getAction();
Log.i(MyTracksConstants.TAG, "enter status onReceive" + antAction);
}
};
Log.i(MyTracksConstants.TAG, "enter onReceive" + antAction);
if (antAction.equals(AntInterfaceIntent.ANT_ENABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_ENABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_ENABLED, 0));
}
if (antAction.equals(AntInterfaceIntent.ANT_DISABLED_ACTION)) {
Log.i(MyTracksConstants.TAG, "enter onReceive ANT_DISABLED_ACTION "
+ antAction);
handler.sendMessage(handler.obtainMessage(EVENT_ANT_DISABLED, 0));
}
/** Receives all data ANT intents. */
private final BroadcastReceiver dataReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String antAction = intent.getAction();
Log.i(MyTracksConstants.TAG, "enter data onReceive" + antAction);
if (antAction.equals(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION)) {
byte[] antMessage =
intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
byte[] antMessage = intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE);
if (DEBUGGING) {
StringBuilder sb =
new StringBuilder("ANT_RX_MESSAGE_ACTION Rx:");
for (int i = 0; i < antMessage.length; i++)
sb.append(String.format("[%X]]", (antMessage[i] & 0xFF)));
Log.d(MyTracksConstants.TAG, sb.toString());
Log.d(MyTracksConstants.TAG, "Received RX message " + messageToString(antMessage));
}
handleMessage(antMessage);
@@ -99,60 +107,39 @@ public abstract class AntSensorManager extends SensorManager {
};
/**
* Handler for all the ANT related events. The events will be handled only
* when we are in the ANT application main menu
**/
private Handler handler = new Handler() {
* ANT uses this listener to tell us when it has bound to the ANT Radio
* Service. We can't start sending ANT commands until we've been notified
* (via this listener) that the Radio Service has connected.
*/
private AntInterface.ServiceListener antServiceListener = new AntInterface.ServiceListener() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_ANT_ENABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_ENABLED");
break;
case EVENT_ANT_DISABLED:
Log.i(MyTracksConstants.TAG,
"enter handleMessage ----EVENT_ANT_DISABLED");
break;
}
public void onServiceConnected() {
serviceConnected();
}
@Override
public void onServiceDisconnected() {
Log.d(MyTracksConstants.TAG, "ANT interface reports disconnection");
}
};
public AntSensorManager(Context context) {
this.context = context;
// Need to enable the ANT if it is not enabled earlier
antReceiver = AntInterface.getInstance(context, null);
if (antReceiver == null) {
return;
}
status = antReceiver.isEnabled();
if (!status) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
status = antReceiver.enable();
if (!status) {
Log.e(MyTracksConstants.TAG, "Can not enable ANT interface");
} else {
Log.i(MyTracksConstants.TAG, "Powering on Radio");
}
} else {
Log.i(MyTracksConstants.TAG, "Radio already enabled");
}
}
}
@Override
public void setupChannel() {
Log.i(MyTracksConstants.TAG, "Registering for ant intents.");
// Register for ANT intent broadcasts.
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
intentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(receiver, intentFilter);
public void onDestroy() {
Log.i(MyTracksConstants.TAG, "destroying AntSensorManager");
context.unregisterReceiver(statusReceiver);
context.unregisterReceiver(dataReceiver);
try {
antReceiver.releaseInterface();
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to release ANT interface", e);
}
antReceiver.destroy();
}
@Override
@@ -165,64 +152,184 @@ public abstract class AntSensorManager extends SensorManager {
return true;
}
public abstract void handleMessage(byte[] antMessage);
@Override
public void onDestroy() {
Log.i(MyTracksConstants.TAG, "Unregistering for ant intents.");
context.unregisterReceiver(receiver);
}
/**
* Set up an ANT+ channel.
*
* @return True on success.
*/
protected boolean antChannelSetup(byte networkNumber, byte channelNumber,
short deviceNumber, byte deviceType, byte txType, short channelPeriod,
byte radioFreq, byte proxSearch) {
// Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 =
// ANTFS)
if (!antReceiver.ANTAssignChannel(channelNumber,
AntDefine.PARAMETER_RX_NOT_TX, networkNumber)) {
return false;
}
if (!antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType,
txType)) {
return false;
}
if (!antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod)) {
return false;
}
if (!antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq)) {
return false;
}
// Disable high priority search
if (!antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0)) {
return false;
}
// Set search timeout to 30 seconds (low priority search))
if (!antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12)) {
return false;
}
if (deviceNumber == WILDCARD) {
// Configure proximity search, if using wild card search
if (!antReceiver.ANTSetProximitySearch(channelNumber, proxSearch)) {
return false;
}
}
return !antReceiver.ANTOpenChannel(channelNumber);
}
public AntInterface getAntReceiver() {
return antReceiver;
}
/**
* This is the interface used by the {@link SensorManager} to tell this
* class when to start. It handles initialization of the ANT framework,
* eventually resulting in sensor-specific initialization via
* {@link #setupAntSensorChannels}.
*/
@Override
protected final void setupChannel() {
// We handle this unpleasantly because the UI should've checked for ANT
// support before it even instantiated this class.
if (!AntInterface.hasAntSupport(context)) {
throw new IllegalStateException("device does not have ANT support");
}
// We register for ANT intents early because we want to have a record of
// the status intents in the log as we start up.
registerForAntIntents();
antReceiver = AntInterface.getInstance(context, antServiceListener);
if (antReceiver == null) {
Log.e(MyTracksConstants.TAG, "Failed to get ANT Receiver");
return;
}
setSensorState(Sensor.SensorState.CONNECTING);
}
/**
* This method is invoked via the ServiceListener when we're connected to
* the ANT service. If we're just starting up, this is our first opportunity
* to initiate any ANT commands.
*/
private synchronized void serviceConnected() {
Log.d(MyTracksConstants.TAG, "ANT service connected");
try {
if (!antReceiver.claimInterface()) {
Log.e(MyTracksConstants.TAG, "failed to claim ANT interface");
return;
}
if (!antReceiver.isEnabled()) {
// Make sure not to call AntInterface.enable() again, if it has been
// already called before
if (antInterrupted == false) {
Log.i(MyTracksConstants.TAG, "Powering on Radio");
antReceiver.enable();
}
} else {
Log.i(MyTracksConstants.TAG, "Radio already enabled");
}
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to enable ANT", e);
}
try {
// We expect this call to throw an exception due to a bug in the ANT
// Radio Service. It won't actually fail, though, as we'll get the
// startup message (see {@link AntStartupMessage}) one normally expects
// after a reset. Channel initialization can proceed once we receive
// that message.
antReceiver.ANTResetSystem();
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to reset ANT (expected exception)", e);
}
}
/**
* Process a raw ANT message.
* @param antMessage the ANT message, including the size and message ID bytes
* @deprecated Use {@link #handleMessage(int, byte[])} instead.
*/
protected void handleMessage(byte[] antMessage) {
int len = antMessage[0];
if (len != antMessage.length - 2 || antMessage.length <= 2) {
Log.e(MyTracksConstants.TAG, "Invalid message: " + messageToString(antMessage));
return;
}
byte messageId = antMessage[1];
// Arrays#copyOfRange doesn't exist??
byte[] messageData = new byte[antMessage.length - 2];
System.arraycopy(antMessage, 2, messageData, 0, antMessage.length - 2);
handleMessage(messageId, messageData);
}
/**
* Process a raw ANT message.
* @param messageId the message ID. See the ANT Message Protocol and Usage
* guide, section 9.3.
* @param messageData the ANT message, without the size and message ID bytes.
* @return true if this method has taken responsibility for the passed
* message; false otherwise.
*/
protected boolean handleMessage(byte messageId, byte[] messageData) {
if (messageId == AntMesg.MESG_STARTUP_MESG_ID) {
Log.d(MyTracksConstants.TAG, String.format(
"Received startup message (reason %02x); initializing channel",
new AntStartupMessage(messageData).getMessage()));
setupAntSensorChannels();
return true;
}
return false;
}
/**
* Subclasses define this method to perform sensor-specific initialization.
* When this method is called, the ANT framework has been enabled, and is
* ready for use.
*/
protected abstract void setupAntSensorChannels();
/**
* Used by subclasses to set up an ANT channel for a single sensor. A given
* subclass may invoke this method multiple times if the subclass is
* responsible for more than one sensor.
*
* @return true on success
*/
protected boolean setupAntSensorChannel(byte networkNumber, byte channelNumber,
short deviceNumber, byte deviceType, byte txType, short channelPeriod,
byte radioFreq, byte proxSearch) {
try {
// Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 =
// ANTFS)
antReceiver.ANTAssignChannel(channelNumber, AntDefine.PARAMETER_RX_NOT_TX, networkNumber);
antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType, txType);
antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod);
antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq);
// Disable high priority search
antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0);
// Set search timeout to 30 seconds (low priority search))
antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12);
if (deviceNumber == WILDCARD) {
// Configure proximity search, if using wild card search
antReceiver.ANTSetProximitySearch(channelNumber, proxSearch);
}
antReceiver.ANTOpenChannel(channelNumber);
return true;
} catch (AntInterfaceException e) {
Log.e(MyTracksConstants.TAG, "failed to setup ANT channel", e);
return false;
}
}
private void registerForAntIntents() {
Log.i(MyTracksConstants.TAG, "Registering for ant intents.");
// Register for ANT intent broadcasts.
IntentFilter statusIntentFilter = new IntentFilter();
statusIntentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_INTERFACE_CLAIMED_ACTION);
statusIntentFilter.addAction(AntInterfaceIntent.ANT_RESET_ACTION);
context.registerReceiver(statusReceiver, statusIntentFilter);
IntentFilter dataIntentFilter = new IntentFilter();
dataIntentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION);
context.registerReceiver(dataReceiver, dataIntentFilter);
}
private String messageToString(byte[] message) {
StringBuilder out = new StringBuilder();
for (byte b : message) {
out.append(String.format("%s%02x", (out.length() == 0 ? "" : " "), b));
}
return out.toString();
}
}
@@ -41,7 +41,7 @@ public abstract class SensorManager {
public static final int RETRY_PERIOD = 30000;
private Sensor.SensorState sensorState = Sensor.SensorState.NONE;
private long sensorStateTimestamp = 0;
/**
@@ -89,7 +89,11 @@ public abstract class SensorManager {
timer.schedule(checkSensorManager, RETRY_PERIOD, RETRY_PERIOD);
}
public abstract void setupChannel();
/**
* This method is used to set up any necessary connections to underlying
* sensor hardware.
*/
protected abstract void setupChannel();
public void shutdown() {
timer.cancel();
@@ -0,0 +1,56 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Channel ID message.
* (ANT Message ID 0x51, Protocol & Usage guide v4.2 section 9.5.7.2)
*
* @author Matthew Simmons
*/
public class AntChannelIdMessage extends AntMessage {
private byte channelNumber;
private short deviceNumber;
private byte deviceTypeId;
private byte transmissionType;
public AntChannelIdMessage(byte[] messageData) {
channelNumber = messageData[0];
deviceNumber = decodeShort(messageData[1], messageData[2]);
deviceTypeId = messageData[3];
transmissionType = messageData[4];
}
/** Returns the channel number */
public byte getChannelNumber() {
return channelNumber;
}
/** Returns the device number */
public short getDeviceNumber() {
return deviceNumber;
}
/** Returns the device type */
public byte getDeviceTypeId() {
return deviceTypeId;
}
/** Returns the transmission type */
public byte getTransmissionType() {
return transmissionType;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Channel Response / Event message.
* (ANT Message ID 0x40, Protocol & Usage guide v4.2 section 9.5.6.1)
*
* @author Matthew Simmons
*/
public class AntChannelResponseMessage extends AntMessage {
private byte channelNumber;
private byte messageId;
private byte messageCode;
public AntChannelResponseMessage(byte[] messageData) {
channelNumber = messageData[0];
messageId = messageData[1];
messageCode = messageData[2];
}
/** Returns the channel number */
public byte getChannelNumber() {
return channelNumber;
}
/** Returns the ID of the message being responded to */
public byte getMessageId() {
return messageId;
}
/** Returns the code for a specific response or event */
public byte getMessageCode() {
return messageCode;
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
/**
* This is a common superclass for ANT message subclasses.
*
* @author Matthew Simmons
*/
public class AntMessage {
protected AntMessage() {}
/** Build a short value from its constituent bytes */
protected static short decodeShort(byte b0, byte b1) {
short value = b0;
value |= ((short) b1) << 8;
return value;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
/**
* This class decodes and encapsulates an ANT Startup message.
* (ANT Message ID 0x6f, Protocol & Usage guide v4.2 section 9.5.3.1)
*
* @author Matthew Simmons
*/
public class AntStartupMessage extends AntMessage {
private byte message;
public AntStartupMessage(byte[] messageData) {
message = messageData[0];
}
/** Returns the cause of the startup */
public byte getMessage() {
return message;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
import com.dsi.ant.AntInterface;
import android.content.Context;
/**
* Utility methods for ANT functionality.
*
* Prefer use of this class to importing DSI ANT classes into code outside of
* the sensors package.
*/
public class AntUtils {
private AntUtils() {}
/** Returns true if this device supports ANT sensors. */
public static boolean hasAntSupport(Context context) {
return AntInterface.hasAntSupport(context);
}
}
@@ -17,6 +17,7 @@
package com.google.android.apps.mytracks.stats;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import android.location.Location;
import android.util.Log;
@@ -89,6 +90,9 @@ public class TripStatisticsBuilder {
*/
private long totalLocations = 0;
private int minRecordingDistance =
MyTracksSettings.DEFAULT_MIN_RECORDING_DISTANCE;
/**
* Creates a new trip starting at the given time.
*
@@ -146,7 +150,7 @@ public class TripStatisticsBuilder {
// Don't do anything if we didn't move since last fix:
double distance = lastLocation.distanceTo(currentLocation);
if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE &&
if (distance < minRecordingDistance &&
currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
lastLocation = currentLocation;
return false;
@@ -359,4 +363,8 @@ public class TripStatisticsBuilder {
// Take a snapshot - we don't want anyone messing with our internals
return new TripStatistics(data);
}
public void setMinRecordingDistance(int minRecordingDistance) {
this.minRecordingDistance = minRecordingDistance;
}
}
@@ -106,6 +106,22 @@ public class ApiFeatures {
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
public boolean isAudioFocusSupported() {
return getApiLevel() >= 8;
}
/**
* There's a bug (#1587) in Cupcake and Donut which prevents you from
* using a SQLiteQueryBuilder twice. That is, if you call buildQuery
* on a given instance (to log the statement for debugging), and then
* call query on the same instance to make it actually do the query,
* it'll regenerate the query for the second call, and will screw it
* up. Specifically, it'll add extra parens which don't belong.
*/
public boolean canReuseSQLiteQueryBuilder() {
return getApiLevel() > 4;
}
// Visible for testing.
protected int getApiLevel() {
@@ -21,12 +21,15 @@ import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.GeoPoint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.location.Location;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.util.ArrayList;
@@ -286,6 +289,42 @@ public class MyTracksUtils {
return "";
}
}
/**
* Tries to acquire a partial wake lock if not already acquired. Logs errors
* and gives up trying in case the wake lock cannot be acquired.
*/
public static WakeLock acquireWakeLock(Activity activity, WakeLock wakeLock) {
Log.i(MyTracksConstants.TAG, "MyTracksUtils: Acquiring wake lock.");
try {
PowerManager pm = (PowerManager) activity
.getSystemService(Context.POWER_SERVICE);
if (pm == null) {
Log.e(MyTracksConstants.TAG, "MyTracksUtils: Power manager not found!");
return wakeLock;
}
if (wakeLock == null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
MyTracksConstants.TAG);
if (wakeLock == null) {
Log.e(MyTracksConstants.TAG,
"MyTracksUtils: Could not create wake lock (null).");
}
return wakeLock;
}
if (!wakeLock.isHeld()) {
wakeLock.acquire();
if (!wakeLock.isHeld()) {
Log.e(MyTracksConstants.TAG,
"MyTracksUtils: Could not acquire wake lock.");
}
}
} catch (RuntimeException e) {
Log.e(MyTracksConstants.TAG,
"MyTracksUtils: Caught unexpected exception: " + e.getMessage(), e);
}
return wakeLock;
}
/**
* This is a utility class w/ only static members.
@@ -13,12 +13,10 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.wireless.gdata.maps.xml;
package com.google.wireless.gdata.docs;
import com.google.wireless.gdata.client.GDataParserFactory;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.maps.MapFeatureEntry;
import com.google.wireless.gdata.maps.MapsClient;
import com.google.wireless.gdata.parser.GDataParser;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
@@ -26,27 +24,22 @@ import com.google.wireless.gdata.parser.xml.XmlParserFactory;
import com.google.wireless.gdata.serializer.GDataSerializer;
import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
import android.util.Log;
import java.io.InputStream;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Factory of Xml parsers for gdata maps data.
*/
public class XmlMapsGDataParserFactory implements GDataParserFactory {
public class XmlDocsGDataParserFactory implements GDataParserFactory {
private XmlParserFactory xmlFactory;
public XmlMapsGDataParserFactory(XmlParserFactory xmlFactory) {
public XmlDocsGDataParserFactory(XmlParserFactory xmlFactory) {
this.xmlFactory = xmlFactory;
}
@Override
public GDataParser createParser(InputStream is) throws ParseException {
is = maybeLogCommunication(is);
try {
return new XmlGDataParser(is, xmlFactory.createParser());
} catch (XmlPullParserException e) {
@@ -59,7 +52,6 @@ public class XmlMapsGDataParserFactory implements GDataParserFactory {
@Override
public GDataParser createParser(Class cls, InputStream is)
throws ParseException {
is = maybeLogCommunication(is);
try {
return createParserForClass(cls, is);
} catch (XmlPullParserException e) {
@@ -68,43 +60,14 @@ public class XmlMapsGDataParserFactory implements GDataParserFactory {
}
}
private InputStream maybeLogCommunication(InputStream is)
throws ParseException {
if (MapsClient.LOG_COMMUNICATION) {
StringBuilder builder = new StringBuilder();
byte[] buffer = new byte[2048];
try {
for (int n = is.read(buffer); n >= 0; n = is.read(buffer)) {
String part = new String(buffer, 0, n);
builder.append(part);
Log.d("Response part", part);
}
} catch (IOException e) {
throw new ParseException("Could not read stream", e);
}
String whole = builder.toString();
Log.d("Response", whole);
is = new ByteArrayInputStream(whole.getBytes());
}
return is;
}
private GDataParser createParserForClass(
Class<? extends Entry> cls, InputStream is)
throws ParseException, XmlPullParserException {
if (cls == MapFeatureEntry.class) {
return new XmlMapsGDataParser(is, xmlFactory.createParser());
} else {
return new XmlGDataParser(is, xmlFactory.createParser());
}
return new XmlGDataParser(is, xmlFactory.createParser());
}
@Override
public GDataSerializer createSerializer(Entry en) {
if (en.getClass() == MapFeatureEntry.class) {
return new XmlMapsGDataSerializer(xmlFactory, en);
} else {
return new XmlEntryGDataSerializer(xmlFactory, en);
}
return new XmlEntryGDataSerializer(xmlFactory, en);
}
}
}
@@ -1,50 +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.wireless.gdata.maps;
import com.google.wireless.gdata.data.Entry;
import java.util.HashMap;
import java.util.Map;
/**
* GData entry for a map feature.
*/
public class MapFeatureEntry extends Entry {
private String mPrivacy = null;
private Map<String, String> mAttributes = new HashMap<String, String>();
public void setPrivacy(String privacy) {
mPrivacy = privacy;
}
public String getPrivacy() {
return mPrivacy;
}
public void setAttribute(String name, String value) {
mAttributes.put(name, value);
}
public void removeAttribute(String name) {
mAttributes.remove(name);
}
public Map<String, String> getAllAttributes() {
return mAttributes;
}
}
@@ -1,113 +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.wireless.gdata.maps;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.GDataParserFactory;
import com.google.wireless.gdata.client.GDataServiceClient;
import android.util.Log;
/**
* Client to talk to Google Maps via GData.
*/
public class MapsClient extends GDataServiceClient {
public static final String SERVICE = "local";
private static final boolean DEBUG = false;
public static final boolean LOG_COMMUNICATION = false;
private static final String MAPS_BASE_FEED_URL =
"http://maps.google.com/maps/feeds/";
private static final String MAPS_MAP_FEED_PATH = "maps/default/full";
private static final String MAPS_FEATURE_FEED_PATH_BEFORE_MAPID = "features/";
private static final String MAPS_FEATURE_FEED_PATH_AFTER_MAPID = "/full";
private static final String MAPS_VERSION_FEED_PATH_FORMAT =
"%smaps/%s/versions/%s/full/%s";
private static final String MAP_ENTRY_ID_BEFORE_USER_ID = "maps/feeds/maps/";
private static final String MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID = "/";
private static final String V2_ONLY_PARAM = "?v=2.0";
public MapsClient(GDataClient dataClient,
GDataParserFactory dataParserFactory) {
super(dataClient, dataParserFactory);
}
@Override
public String getServiceName() {
return SERVICE;
}
public static String getMapsFeed() {
if (DEBUG) {
Log.d("Maps Client", "Requesting map feed:");
}
return MAPS_BASE_FEED_URL + MAPS_MAP_FEED_PATH + V2_ONLY_PARAM;
}
public static String getFeaturesFeed(String mapid) {
StringBuilder feed = new StringBuilder();
feed.append(MAPS_BASE_FEED_URL);
feed.append(MAPS_FEATURE_FEED_PATH_BEFORE_MAPID);
feed.append(mapid);
feed.append(MAPS_FEATURE_FEED_PATH_AFTER_MAPID);
feed.append(V2_ONLY_PARAM);
return feed.toString();
}
public static String getMapIdFromMapEntryId(String entryId) {
String userId = null;
String mapId = null;
if (DEBUG) {
Log.d("Maps GData Client", "Getting mapid from entry id: " + entryId);
}
int userIdStart =
entryId.indexOf(MAP_ENTRY_ID_BEFORE_USER_ID)
+ MAP_ENTRY_ID_BEFORE_USER_ID.length();
int userIdEnd =
entryId.indexOf(MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID, userIdStart);
if (userIdStart >= 0 && userIdEnd < entryId.length()
&& userIdStart <= userIdEnd) {
userId = entryId.substring(userIdStart, userIdEnd);
}
int mapIdStart =
entryId.indexOf(MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID, userIdEnd)
+ MAP_ENTRY_ID_BETWEEN_USER_ID_AND_MAP_ID.length();
if (mapIdStart >= 0 && mapIdStart < entryId.length()) {
mapId = entryId.substring(mapIdStart);
}
if (userId == null) {
userId = "";
}
if (mapId == null) {
mapId = "";
}
if (DEBUG) {
Log.d("Maps GData Client", "Got user id: " + userId);
Log.d("Maps GData Client", "Got map id: " + mapId);
}
return userId + "." + mapId;
}
public static String getVersionFeed(String versionUserId,
String versionClient, String currentVersion) {
return String.format(MAPS_VERSION_FEED_PATH_FORMAT,
MAPS_BASE_FEED_URL, versionUserId,
versionClient, currentVersion);
}
}
@@ -1,178 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.wireless.gdata.maps.xml;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.Feed;
import com.google.wireless.gdata.data.XmlUtils;
import com.google.wireless.gdata.maps.MapFeatureEntry;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
/**
* Parser for XML gdata maps data.
*/
public class XmlMapsGDataParser extends XmlGDataParser {
public XmlMapsGDataParser(InputStream is, XmlPullParser xpp)
throws ParseException {
super(is, xpp);
}
@Override
protected Feed createFeed() {
return new Feed();
}
@Override
protected Entry createEntry() {
return new MapFeatureEntry();
}
@Override
protected void handleExtraElementInFeed(Feed feed) {
// Do nothing
}
@Override
protected void handleExtraLinkInEntry(
String rel, String type, String href, Entry entry)
throws XmlPullParserException, IOException {
if (!(entry instanceof MapFeatureEntry)) {
throw new IllegalArgumentException("Expected MapFeatureEntry!");
}
if (rel.endsWith("#view")) {
return;
}
super.handleExtraLinkInEntry(rel, type, href, entry);
}
/**
* Parses the current entry in the XML document. Assumes that the parser is
* currently pointing just after an &lt;entry&gt;.
*
* @param plainEntry The entry that will be filled.
* @throws XmlPullParserException Thrown if the XML cannot be parsed.
* @throws IOException Thrown if the underlying inputstream cannot be read.
*/
@Override
protected void handleEntry(Entry plainEntry)
throws XmlPullParserException, IOException, ParseException {
XmlPullParser parser = getParser();
if (!(plainEntry instanceof MapFeatureEntry)) {
throw new IllegalArgumentException("Expected MapFeatureEntry!");
}
MapFeatureEntry entry = (MapFeatureEntry) plainEntry;
int eventType = parser.getEventType();
entry.setPrivacy("public");
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
String name = parser.getName();
if ("entry".equals(name)) {
// stop parsing here.
return;
} else if ("id".equals(name)) {
entry.setId(XmlUtils.extractChildText(parser));
} else if ("title".equals(name)) {
entry.setTitle(XmlUtils.extractChildText(parser));
} else if ("link".equals(name)) {
String rel = parser.getAttributeValue(null /* ns */, "rel");
String type = parser.getAttributeValue(null /* ns */, "type");
String href = parser.getAttributeValue(null /* ns */, "href");
if ("edit".equals(rel)) {
entry.setEditUri(href);
} else if ("alternate".equals(rel) && "text/html".equals(type)) {
entry.setHtmlUri(href);
} else {
handleExtraLinkInEntry(rel, type, href, entry);
}
} else if ("summary".equals(name)) {
entry.setSummary(XmlUtils.extractChildText(parser));
} else if ("content".equals(name)) {
StringBuilder contentBuilder = new StringBuilder();
int parentDepth = parser.getDepth();
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
int etype = parser.next();
switch (etype) {
case XmlPullParser.START_TAG:
contentBuilder.append('<');
contentBuilder.append(parser.getName());
contentBuilder.append('>');
break;
case XmlPullParser.TEXT:
contentBuilder.append("<![CDATA[");
contentBuilder.append(parser.getText());
contentBuilder.append("]]>");
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() > parentDepth) {
contentBuilder.append("</");
contentBuilder.append(parser.getName());
contentBuilder.append('>');
}
break;
}
if (etype == XmlPullParser.END_TAG
&& parser.getDepth() == parentDepth) {
break;
}
}
entry.setContent(contentBuilder.toString());
} else if ("category".equals(name)) {
String category = parser.getAttributeValue(null /* ns */, "term");
if (category != null && category.length() > 0) {
entry.setCategory(category);
}
String categoryScheme =
parser.getAttributeValue(null /* ns */, "scheme");
if (categoryScheme != null && category.length() > 0) {
entry.setCategoryScheme(categoryScheme);
}
} else if ("published".equals(name)) {
entry.setPublicationDate(XmlUtils.extractChildText(parser));
} else if ("updated".equals(name)) {
entry.setUpdateDate(XmlUtils.extractChildText(parser));
} else if ("deleted".equals(name)) {
entry.setDeleted(true);
} else if ("draft".equals(name)) {
String draft = XmlUtils.extractChildText(parser);
entry.setPrivacy("yes".equals(draft) ? "unlisted" : "public");
} else if ("customProperty".equals(name)) {
String attrName = parser.getAttributeValue(null, "name");
String attrValue = XmlUtils.extractChildText(parser);
entry.setAttribute(attrName, attrValue);
} else if ("deleted".equals(name)) {
entry.setDeleted(true);
} else {
handleExtraElementInEntry(entry);
}
break;
default:
break;
}
eventType = parser.next();
}
}
}
@@ -1,277 +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.wireless.gdata.maps.xml;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.StringUtils;
import com.google.wireless.gdata.maps.MapFeatureEntry;
import com.google.wireless.gdata.maps.MapsClient;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
import com.google.wireless.gdata.parser.xml.XmlParserFactory;
import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
/**
* Serializer of maps data for GData.
*/
public class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
private static final String APP_NAMESPACE = "http://www.w3.org/2007/app";
private MapFeatureEntry entry;
private XmlParserFactory factory;
private OutputStream stream;
public XmlMapsGDataSerializer(XmlParserFactory factory, Entry entry) {
super(factory, entry);
this.factory = factory;
this.entry = (MapFeatureEntry) entry;
}
@Override
public void serialize(OutputStream out, int format)
throws IOException, ParseException {
XmlSerializer serializer = null;
try {
serializer = factory.createSerializer();
} catch (XmlPullParserException e) {
throw new ParseException("Unable to create XmlSerializer.", e);
}
// TODO: make the output compact
ByteArrayOutputStream printStream;
if (MapsClient.LOG_COMMUNICATION) {
printStream = new ByteArrayOutputStream();
serializer.setOutput(printStream, "UTF-8");
} else {
serializer.setOutput(out, "UTF-8");
}
serializer.startDocument("UTF-8", Boolean.FALSE);
declareEntryNamespaces(serializer);
serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
stream = MapsClient.LOG_COMMUNICATION ? printStream : out;
serializeEntryContents(serializer, format);
serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
serializer.endDocument();
serializer.flush();
if (MapsClient.LOG_COMMUNICATION) {
Log.d("Request", printStream.toString());
out.write(printStream.toByteArray());
stream = out;
}
}
private final void declareEntryNamespaces(XmlSerializer serializer)
throws IOException {
serializer.setPrefix(
"" /* default ns */, XmlGDataParser.NAMESPACE_ATOM_URI);
serializer.setPrefix(
XmlGDataParser.NAMESPACE_GD, XmlGDataParser.NAMESPACE_GD_URI);
declareExtraEntryNamespaces(serializer);
}
private final void serializeEntryContents(XmlSerializer serializer,
int format) throws IOException {
if (format != FORMAT_CREATE) {
serializeId(serializer, entry.getId());
}
serializeTitle(serializer, entry.getTitle());
if (format != FORMAT_CREATE) {
serializeLink(serializer,
"edit" /* rel */, entry.getEditUri(), null /* type */);
serializeLink(serializer,
"alternate" /* rel */, entry.getHtmlUri(), "text/html" /* type */);
}
serializeSummary(serializer, entry.getSummary());
serializeContent(serializer, entry.getContent());
serializeAuthor(serializer, entry.getAuthor(), entry.getEmail());
serializeCategory(serializer,
entry.getCategory(), entry.getCategoryScheme());
if (format == FORMAT_FULL) {
serializePublicationDate(serializer, entry.getPublicationDate());
}
if (format != FORMAT_CREATE) {
serializeUpdateDate(serializer, entry.getUpdateDate());
}
serializeExtraEntryContents(serializer, format);
}
private static void serializeId(XmlSerializer serializer, String id)
throws IOException {
if (StringUtils.isEmpty(id)) {
return;
}
serializer.startTag(null /* ns */, "id");
serializer.text(id);
serializer.endTag(null /* ns */, "id");
}
private static void serializeTitle(XmlSerializer serializer, String title)
throws IOException {
if (StringUtils.isEmpty(title)) {
return;
}
serializer.startTag(null /* ns */, "title");
serializer.text(title);
serializer.endTag(null /* ns */, "title");
}
public static void serializeLink(XmlSerializer serializer, String rel,
String href, String type) throws IOException {
if (StringUtils.isEmpty(href)) {
return;
}
serializer.startTag(null /* ns */, "link");
serializer.attribute(null /* ns */, "rel", rel);
serializer.attribute(null /* ns */, "href", href);
if (!StringUtils.isEmpty(type)) {
serializer.attribute(null /* ns */, "type", type);
}
serializer.endTag(null /* ns */, "link");
}
private static void serializeSummary(XmlSerializer serializer, String summary)
throws IOException {
if (StringUtils.isEmpty(summary)) {
return;
}
serializer.startTag(null /* ns */, "summary");
serializer.text(summary);
serializer.endTag(null /* ns */, "summary");
}
private void serializeContent(XmlSerializer serializer, String content)
throws IOException {
if (content == null) {
return;
}
serializer.startTag(null /* ns */, "content");
if (content.contains("</Placemark>")) {
serializer.attribute(
null /* ns */, "type", "application/vnd.google-earth.kml+xml");
serializer.flush();
stream.write(content.getBytes());
} else {
serializer.text(content);
}
serializer.endTag(null /* ns */, "content");
}
private static void serializeAuthor(XmlSerializer serializer, String author,
String email) throws IOException {
if (StringUtils.isEmpty(author) || StringUtils.isEmpty(email)) {
return;
}
serializer.startTag(null /* ns */, "author");
serializer.startTag(null /* ns */, "name");
serializer.text(author);
serializer.endTag(null /* ns */, "name");
serializer.startTag(null /* ns */, "email");
serializer.text(email);
serializer.endTag(null /* ns */, "email");
serializer.endTag(null /* ns */, "author");
}
private static void serializeCategory(XmlSerializer serializer,
String category, String categoryScheme) throws IOException {
if (StringUtils.isEmpty(category) && StringUtils.isEmpty(categoryScheme)) {
return;
}
serializer.startTag(null /* ns */, "category");
if (!StringUtils.isEmpty(category)) {
serializer.attribute(null /* ns */, "term", category);
}
if (!StringUtils.isEmpty(categoryScheme)) {
serializer.attribute(null /* ns */, "scheme", categoryScheme);
}
serializer.endTag(null /* ns */, "category");
}
private static void serializePublicationDate(XmlSerializer serializer,
String publicationDate) throws IOException {
if (StringUtils.isEmpty(publicationDate)) {
return;
}
serializer.startTag(null /* ns */, "published");
serializer.text(publicationDate);
serializer.endTag(null /* ns */, "published");
}
private static void serializeUpdateDate(XmlSerializer serializer,
String updateDate) throws IOException {
if (StringUtils.isEmpty(updateDate)) {
return;
}
serializer.startTag(null /* ns */, "updated");
serializer.text(updateDate);
serializer.endTag(null /* ns */, "updated");
}
@Override
protected void serializeExtraEntryContents(XmlSerializer serializer,
int format) throws IOException {
Map<String, String> attrs = entry.getAllAttributes();
for (Map.Entry<String, String> attr : attrs.entrySet()) {
serializer.startTag("http://schemas.google.com/g/2005", "customProperty");
serializer.attribute(null, "name", attr.getKey());
serializer.text(attr.getValue());
serializer.endTag("http://schemas.google.com/g/2005", "customProperty");
}
String privacy = entry.getPrivacy();
if (!StringUtils.isEmpty(privacy)) {
serializer.setPrefix("app", APP_NAMESPACE);
if ("public".equals(privacy)) {
serializer.startTag(APP_NAMESPACE, "control");
serializer.startTag(APP_NAMESPACE, "draft");
serializer.text("no");
serializer.endTag(APP_NAMESPACE, "draft");
serializer.endTag(APP_NAMESPACE, "control");
}
if ("unlisted".equals(privacy)) {
serializer.startTag(APP_NAMESPACE, "control");
serializer.startTag(APP_NAMESPACE, "draft");
serializer.text("yes");
serializer.endTag(APP_NAMESPACE, "draft");
serializer.endTag(APP_NAMESPACE, "control");
}
}
}
}
@@ -30,57 +30,41 @@ public class GpxTrackWriterTest extends TrackFormatWriterTest {
assertEquals(Long.toString(TRACK_ID),
getChildTextValue(trackTag, "number"));
List<Element> segmentTags = getChildElements(trackTag, "trkseg", 2);
Element segment1Tag = segmentTags.get(0);
Element segment2Tag = segmentTags.get(1);
List<Element> seg1PointTags = getChildElements(segment1Tag, "trkpt", 2);
List<Element> seg2PointTags = getChildElements(segment2Tag, "trkpt", 2);
assertTagsMatchPoints(seg1PointTags, location1, location2);
assertTagsMatchPoints(seg2PointTags, location3, location4);
List<Element> segPointTags = getChildElements(segmentTags.get(0), "trkpt", 2);
assertTagMatchesLocation(segPointTags.get(0),
"0", "0", "1970-01-01T02:46:40Z", "0");
assertTagMatchesLocation(segPointTags.get(1),
"1", "-1", "1970-01-01T02:46:41Z", "5000000");
segPointTags = getChildElements(segmentTags.get(1), "trkpt", 2);
assertTagMatchesLocation(segPointTags.get(0),
"2", "-2", "1970-01-01T02:46:42Z", "10000000");
assertTagMatchesLocation(segPointTags.get(1),
"3", "-3", "1970-01-01T02:46:43Z", "15000000");
List<Element> waypointTags = getChildElements(gpxTag, "wpt", 2);
assertTagsMatchWaypoints(waypointTags, wp1, wp2);
Element wptTag = waypointTags.get(0);
assertEquals(WAYPOINT1_NAME, getChildTextValue(wptTag, "name"));
assertEquals(WAYPOINT1_DESCRIPTION, getChildTextValue(wptTag, "desc"));
assertTagMatchesLocation(wptTag,
"1", "-1", "1970-01-01T02:46:41Z", "5000000");
wptTag = waypointTags.get(1);
assertEquals(WAYPOINT2_NAME, getChildTextValue(wptTag, "name"));
assertEquals(WAYPOINT2_DESCRIPTION, getChildTextValue(wptTag, "desc"));
assertTagMatchesLocation(wptTag,
"2", "-2", "1970-01-01T02:46:42Z", "10000000");
}
/**
* Asserts that the given tags describe the given waypoints, in the same
* order.
* Asserts that the given tag describes the location given by the
* Strings lat, lon, time, and ele.
*/
protected void assertTagsMatchWaypoints(List<Element> tags, Waypoint... wps) {
assertEquals(wps.length, tags.size());
for (int i = 0; i < wps.length; i++) {
Element tag = tags.get(i);
Waypoint wp = wps[i];
Location loc = wp.getLocation();
assertTagMatchesLocation(tag, loc);
assertEquals(wp.getName(), getChildTextValue(tag, "name"));
assertEquals(wp.getDescription(), getChildTextValue(tag, "desc"));
}
}
/**
* Asserts that the given tags describe the given points, in the same order.
*/
protected void assertTagsMatchPoints(List<Element> tags, Location... locs) {
assertEquals(locs.length, tags.size());
for (int i = 0; i < locs.length; i++) {
Element tag = tags.get(i);
Location loc = locs[i];
assertTagMatchesLocation(tag, loc);
}
}
/**
* Asserts that the given tag describes the given location.
*/
private void assertTagMatchesLocation(Element tag, Location loc) {
assertEquals(Double.toString(loc.getLatitude()), tag.getAttribute("lat"));
assertEquals(Double.toString(loc.getLongitude()), tag.getAttribute("lon"));
assertEquals(
GpxTrackWriter.TIMESTAMP_FORMAT.format(new Date(loc.getTime())),
getChildTextValue(tag, "time"));
assertEquals(Double.toString(loc.getAltitude()),
getChildTextValue(tag, "ele"));
private void assertTagMatchesLocation(Element tag, String lat,
String lon, String time, String ele) {
assertEquals(lat, tag.getAttribute("lat"));
assertEquals(lon, tag.getAttribute("lon"));
assertEquals(time, getChildTextValue(tag, "time"));
assertEquals(ele, getChildTextValue(tag, "ele"));
}
}
@@ -81,7 +81,7 @@ public abstract class TrackFormatWriterTest extends AndroidTestCase {
protected void populateLocations(MyTracksLocation... locs) {
for (int i = 0; i < locs.length; i++) {
MyTracksLocation loc = locs[i];
loc.setAltitude(i * 1000);
loc.setAltitude(i * 5000000);
loc.setLatitude(i);
loc.setLongitude(-i);
loc.setTime(10000000 + i * 1000);
@@ -267,8 +267,13 @@ public class TrackWriterTest extends AndroidTestCase {
wpt.setId(i + 1);
}
// Begin the track
formatWriter.writeHeader();
// Expect reading/writing of the waypoints (except the first)
formatWriter.writeWaypoint(wptEq(wps[1]));
formatWriter.writeWaypoint(wptEq(wps[2]));
// Begin the track
formatWriter.writeBeginTrack(locEq(locs[0]));
// Write locations 1-2
@@ -289,10 +294,6 @@ public class TrackWriterTest extends AndroidTestCase {
// End the track
formatWriter.writeEndTrack(locEq(locs[5]));
// Expect reading/writing of the waypoints (except the first)
formatWriter.writeWaypoint(wptEq(wps[1]));
formatWriter.writeWaypoint(wptEq(wps[2]));
formatWriter.writeFooter();
formatWriter.close();
@@ -1,12 +1,12 @@
/*
* 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
@@ -28,7 +28,7 @@ public class AntDirectSensorManagerTest extends AndroidTestCase {
private SharedPreferences sharedPreferences;
private AntDirectSensorManager manager;
public void setUp() {
sharedPreferences = getContext().getSharedPreferences(
MyTracksSettings.SETTINGS_NAME, 0);
@@ -36,14 +36,16 @@ public class AntDirectSensorManagerTest extends AndroidTestCase {
sharedPreferences.edit().clear().commit();
manager = new AntDirectSensorManager(getContext());
}
@SuppressWarnings("deprecation")
@SmallTest
public void testBroadcastData() {
manager.setDeviceNumberHRM((short) 42);
byte[] buff = new byte[11];
buff[0] = 9;
buff[1] = AntMesg.MESG_BROADCAST_DATA_ID;
buff[2] = 0; // HRM CHANNEL
buff[10] = (byte) 220;
buff[10] = (byte) 220;
manager.handleMessage(buff);
Sensor.SensorDataSet sds = manager.getSensorDataSet();
@@ -52,24 +54,16 @@ public class AntDirectSensorManagerTest extends AndroidTestCase {
assertEquals(Sensor.SensorState.SENDING,
sds.getHeartRate().getState());
assertEquals(220, sds.getHeartRate().getValue());
assertFalse(sds.hasCadence());
assertFalse(sds.hasPower());
}
@SmallTest
public void testHandleChannelId() {
assertEquals(0, manager.getDeviceNumberHRM());
short id = 42;
byte[] buff = new byte[5];
buff[3] = 42;
manager.handleChannelId(buff);
assertEquals(id, manager.getDeviceNumberHRM());
}
@SuppressWarnings("deprecation")
@SmallTest
public void testChannelId() {
byte[] buff = new byte[11];
buff[0] = 9;
buff[1] = AntMesg.MESG_CHANNEL_ID_ID;
buff[3] = 42;
manager.handleMessage(buff);
@@ -81,17 +75,21 @@ public class AntDirectSensorManagerTest extends AndroidTestCase {
assertNull(manager.getSensorDataSet());
}
@SuppressWarnings("deprecation")
@SmallTest
public void testResponseEvent() {
assertEquals(Sensor.SensorState.NONE, manager.getSensorState());
byte[] buff = new byte[11];
byte[] buff = new byte[5];
buff[0] = 3; // length
buff[1] = AntMesg.MESG_RESPONSE_EVENT_ID;
buff[2] = 0; // channel
buff[3] = AntMesg.MESG_UNASSIGN_CHANNEL_ID;
buff[4] = 0; // code
manager.handleMessage(buff);
assertEquals(Sensor.SensorState.DISCONNECTED, manager.getSensorState());
assertNull(manager.getSensorDataSet());
}
// TODO: Test timeout too.
}
@@ -0,0 +1,85 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
public class AntSensorManagerTest extends AndroidTestCase {
private class TestAntSensorManager extends AntSensorManager {
public byte messageId;
public byte[] messageData;
public TestAntSensorManager() {
super(null);
}
@Override
protected void setupAntSensorChannels() {}
@SuppressWarnings("deprecation")
@Override
public void handleMessage(byte[] rawMessage) {
super.handleMessage(rawMessage);
}
@Override
public boolean handleMessage(byte messageId, byte[] messageData) {
this.messageId = messageId;
this.messageData = messageData;
return true;
}
}
private final TestAntSensorManager sensorManager = new TestAntSensorManager();
public void testSimple() {
byte[] rawMessage = {
0x03, // length
0x12, // message id
0x11, 0x22, 0x33, // body
};
byte[] expectedBody = { 0x11, 0x22, 0x33 };
sensorManager.handleMessage(rawMessage);
assertEquals((byte) 0x12, sensorManager.messageId);
MoreAsserts.assertEquals(expectedBody, sensorManager.messageData);
}
public void testTooShort() {
byte[] rawMessage = {
0x53, // length
0x12 // message id
};
sensorManager.handleMessage(rawMessage);
assertEquals(0, sensorManager.messageId);
assertNull(sensorManager.messageData);
}
public void testLengthWrong() {
byte[] rawMessage = {
0x53, // length
0x12, // message id
0x34, // body
};
sensorManager.handleMessage(rawMessage);
assertEquals(0, sensorManager.messageId);
assertNull(sensorManager.messageData);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
import android.test.AndroidTestCase;
public class AntChannelIdMessageTest extends AndroidTestCase {
public void testParse() {
byte[] rawMessage = {
0, // channel number
0x34, 0x12, // device number
(byte) 0xaa, // device type id
(byte) 0xbb, // transmission type
};
AntChannelIdMessage message = new AntChannelIdMessage(rawMessage);
assertEquals(0, message.getChannelNumber());
assertEquals(0x1234, message.getDeviceNumber());
assertEquals((byte) 0xaa, message.getDeviceTypeId());
assertEquals((byte) 0xbb, message.getTransmissionType());
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
import com.dsi.ant.AntDefine;
import com.dsi.ant.AntMesg;
import android.test.AndroidTestCase;
public class AntChannelResponseMessageTest extends AndroidTestCase {
public void testParse() {
byte[] rawMessage = {
0,
AntMesg.MESG_EVENT_ID,
AntDefine.EVENT_RX_SEARCH_TIMEOUT
};
AntChannelResponseMessage message = new AntChannelResponseMessage(rawMessage);
assertEquals(0, message.getChannelNumber());
assertEquals(AntMesg.MESG_EVENT_ID, message.getMessageId());
assertEquals(AntDefine.EVENT_RX_SEARCH_TIMEOUT, message.getMessageCode());
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
import android.test.AndroidTestCase;
public class AntMessageTest extends AndroidTestCase {
private static class TestAntMessage extends AntMessage {
public static short decodeShort(byte low, byte high) {
return AntMessage.decodeShort(low, high);
}
}
public void testDecode() {
assertEquals(0x1234, TestAntMessage.decodeShort((byte) 0x34, (byte) 0x12));
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors.ant;
import android.test.AndroidTestCase;
public class AntStartupMessageTest extends AndroidTestCase {
public void testParse() {
byte[] rawMessage = {
0x12,
};
AntStartupMessage message = new AntStartupMessage(rawMessage);
assertEquals(0x12, message.getMessage());
}
}