Compare commits

...

90 Commits

Author SHA1 Message Date
Jimmy Shih 008fa88012 Update AndroidManifest.xml versionCode and versionName
for the next release.
2011-10-11 18:15:16 -07:00
Jimmy Shih 4c49497577 Fix comment on ad70806c8dc7 2011-10-11 16:36:10 -07:00
Jimmy Shih 8dd3741fa7 Fix comments for ad70806c8dc7 2011-10-11 16:19:54 -07:00
Jimmy Shih 5572e07b34 - Use BluetoothDevice createInsecureRfcommSocketToServiceRecord for API level 10 or above.
- Use DecimalFormatSymbols getInstance for API level 9 or above.
- Use Arrays copyOfRange for API level 9 or above.
- Remove ApiFeatures hasStrictMode.
- Fix comment on MapOverlay to indicate that the two methods should only be
  used during testing.
- Use android.text.format.DateFormat to format date and time in DocsHelper.
  This DateFormat uses the same locale format as the phone configuration.

Fix DecimalFormat
2011-10-11 14:54:49 -07:00
Jimmy Shih 3f5589b655 Clean up AndroidManifest.xml for SignalStrengthSampler. 2011-10-11 09:52:35 -07:00
Jimmy Shih 48abfdd621 Fix SignalStrengthSampler.
Fix style comment on TrackRecordingService.
2011-10-10 18:11:51 -07:00
Jimmy Shih 06088b807b Fix BluetoothConnectionManager by using the public api
to create an insecure connection.
2011-10-10 17:12:17 -07:00
Jimmy Shih 21e47d8ce0 Fix unit tests with TrackPathDescriptor.
In MapOverlay, for testing, expose a getter and a setter
for TrackPathDescriptor instead of newPath and getLastPath.

In SingleColorTrackPathPainter, refactor "new Path" to a
newPath method so that tests can override the newPath method
to provide a mockPath.
2011-10-10 16:47:59 -07:00
Jimmy Shih c39ab2fda2 - fix bluetooth connection manager when creating an insecure connection
- in ControlRecordingService, unbind right after handling an intent
  to prevent leaking ServiceConnection before handling the next intent.
- update comment in TrackRecordingService
2011-10-10 15:03:54 -07:00
Jimmy Shih 4197120d92 merge heads 2011-10-10 12:13:01 -07:00
Jimmy Shih da7bd0f4ea Update "Allow access" texts and variables
Change notification permission to dangerous
Sort entries in keys.xml alphabetically
2011-10-10 12:10:12 -07:00
Jimmy Shih 21ac283355 merge heads 2011-10-09 15:05:10 -07:00
Jimmy Shih 153f638e03 merge heads 2011-10-09 14:46:53 -07:00
Jimmy Shih 9156735aa0 Add a confirmation dialog when user allows access to other apps 2011-10-09 14:36:09 -07:00
Jimmy Shih b8ad90a4cb protect MyTracksProvider based on settings bit, version 2 2011-10-09 13:56:47 -07:00
Jimmy Shih 36f7ba71f9 Fix unit tests 2011-10-09 13:42:02 -07:00
Jimmy Shih b1af2089a4 merge heads 2011-10-09 13:13:30 -07:00
Jimmy Shih 2b50a314a5 backout changeset ced20b29c911 2011-10-09 13:13:01 -07:00
Jimmy Shih 376d02cce2 Protects MyTracks notifications based on the settings bit.
Only sends broadcast notifications if allowed by the settings bit.

Introduces a permission for internal MyTracks communication.
Thus the MyTracks app widget can use this permission to
always receive broadcast notifications when a track starts
and stops.
2011-10-07 18:05:23 -07:00
Jimmy Shih 9e5204934f Protect MyTracksProvider based on the MyTracks settings bit.
Move MyTracksProvider to DatabaseProvider and protect the DatabaseProvider
using the AndroidManifest.xml.

Create a MyTracksProviderUtilFactory to create an instance of
MyTracksProviderUtils that accesses the DatabaseProvider.

Recreate MyTracksProvider by extending DatabaseProvider.
The recreated provider checks access, changes the authority,
and invokes the DatabaseProvider methods.
2011-10-07 16:23:59 -07:00
Jimmy Shih 1ffd77595d Improve TrackRecordingService by placing checkService inside canAccess 2011-10-06 10:09:25 -07:00
Jimmy Shih dac6a362f2 merge heads 2011-10-06 09:54:21 -07:00
Umran Abdulla 6d29b5140b Fix for the exception thrown when remotely starting bluetooth sensors. 2011-10-06 15:02:39 +11:00
Umran Abdulla 2c5414a8e7 Makes changes to the sensor information screen, to allow sensor details to be
available when the application is not recording.
2011-10-06 14:26:10 +11:00
Jimmy Shih 2fa325dbf3 Protect TrackRecordingService to allow
external applications access based on the
sharing settings.

Protect all RPCs calls using the caller's
process id.

When the service is started, we cannot
tell whether the caller is MyTracks or
another app. Thus when the service
is started, we cannot access MyTracks
database or start/stop a recording.

Previsouly, only the MyTracks app
widget is using the service's start method
to start and stop a recording. Thus
create a private service, ControlRecordingService,
that only the MyTracks app widget can access and delegate
starting/stopping a recording to
TrackRecordingService.
2011-10-05 16:45:32 -07:00
Jimmy Shih 969a677d7d Add a Share data settings 2011-10-04 18:35:37 -07:00
Jimmy Shih 90470342e7 merge heads 2011-10-04 14:51:45 -07:00
Jimmy Shih a4e762fb01 update MyTracks permission labels and descriptions 2011-10-04 14:50:00 -07:00
Jimmy Shih da2d732041 fix proguard issues with google-api-client-1.5.0 2011-10-04 13:57:27 -07:00
Jimmy Shih 6a5acee438 merge heads 2011-09-29 18:14:44 -07:00
Jimmy Shih 49cd5014d2 merge heads 2011-09-28 19:41:18 -07:00
Josh Kasten 522882c0cc Added back throwing of IllegalStateExceptions to startNewTrack and
endCurrentTrack so they still are thrown when called from the
ServiceBinder methods.
2011-09-28 21:11:45 -05:00
Josh Kasten 7e8b4dc4b9 Removed ignore_illegal_state string and code the uses it.
Removed the throwing of exceptions on start and stop of tracks and instead
added checking to not run them.
2011-09-28 20:22:02 -05:00
Jimmy Shih 8e673f6634 Remove clearTrackDataHub in MyTracksApplication.java 2011-09-28 17:54:11 -07:00
Josh Kasten 05e6018bd1 Organized code in handleStartCommand into smaller methods to better see the code
paths and for eaier maintainability.
2011-09-28 19:22:49 -05:00
ase69s e1a845d49f I think i have done the commit correctly, althought i only had to correct manually keys.xml (add new key from the head branch xml to mine) 2011-09-29 01:34:43 +02:00
Josh Kasten 2deded2d88 Renamed omit_illegal_state to android.intent.extra.IGNORE_ILLEGAL_STATE
Code comment and code style change.
2011-09-27 22:49:53 -05:00
Josh Kasten af550a7104 Fixed widget to not cause a unhandled exception to occur if you press on the widget
before it can update its button intent.
2011-09-27 19:59:33 -05:00
Jimmy Shih e68d7eb12d Upgrade google-api-client from 1.2.2-alpha to 1.5.0-beta. 2011-09-27 14:38:31 -07:00
Jimmy Shih a186e68ca0 Fix based on Sandor's comments 2011-09-26 11:51:05 -07:00
Jimmy Shih 81c60ca6f2 merge heads 2011-09-26 11:27:34 -07:00
Jimmy Shih bd9347afd7 merge heads 2011-09-26 10:54:11 -07:00
Jimmy Shih 9023cdf726 merge heads 2011-09-26 10:53:43 -07:00
Jimmy Shih 9caef87899 Automated merge with http://jkasten-mytracks2.googlecode.com/hg 2011-09-23 09:26:21 -07:00
ase69s c9d9c065a2 Finally solved instanceof class test. Its ready to merge? 2011-09-23 17:54:58 +02:00
Jimmy Shih c024c8d87b Fix proguard issue with fusion table.
Fusion table depends on google-api-client-1.2.2.alpha.jar.
Proguard needs to be set correctly for the google-api-client library.

Tested a release version and is able to send a track to fusion table.
2011-09-22 18:47:31 -07:00
Josh Kasten 850a0d988a Fixed Next key on the virtual keyboard on the "Marker Type" field to the next
field in the waypoint and track details activities.
2011-09-22 20:29:46 -05:00
Jimmy Shih 7bd3128e8f In MyTracks onDestroy, set trackDataHub to null to save memory 2011-09-22 12:22:23 -07:00
Jimmy Shih 43a1ec60ce create MyTracksApplication to keep global state like TrackDataHub 2011-09-22 11:25:27 -07:00
ase69s d04d83b972 Theorically solved last problem with tests...anything more? 2011-09-22 19:22:28 +02:00
Josh Kasten 89516c41e3 Added hints to all text input fields with the same value as their label for
all languages.
2011-09-22 00:03:57 -05:00
Jimmy Shih 4f9b6535fb Automated merge with http://mytracks.googlecode.com/hg/ 2011-09-21 16:17:01 -07:00
Jimmy Shih 6ab1f6158a Fixes various unit tests 2011-09-21 16:16:37 -07:00
Josh Kasten bb8e6a51f4 * Added display_settings_screen_key to keys.xml and put it to use in
CheckUnits and SettingsActivity classes for use with intents.

* In SettingsActivity split onCreate() code into initActivityCommons() and
  initActivitySpecifics() methods for easier maintaining of code required
  for all screens and code require for only some screens.
2011-09-20 19:52:53 -05:00
ase69s 7a8ea2666b Review issues corrected, pending issue about "expect" with class type argument 2011-09-20 22:31:59 +02:00
Josh Kasten 3029c6d10c * Changed SettingsActivity and CheckUnits
to use open_settings_screen instead of "Open".

* Cleaned up SettingsActivity by removing the class scoped variable displaySettingsOnly.
2011-09-19 17:54:00 -05:00
Josh Kasten 9bb0ba2953 Merge from main repo 2011-09-19 17:38:32 -05:00
Sandor Dornbush 0e22eaf7c3 Merging head. 2011-09-19 13:43:58 -07:00
ase69s 53ee9d31bc Review issues corrected, added test case for remaining class TrackPathPainterFactory 2011-09-19 20:25:41 +02:00
Jimmy Shih 7134edf2f2 Create DialogButton and DialogButtonSpacer styles.
Modify mytracks_send_to_google.xml dialog to use these styles.
2011-09-15 11:20:20 -07:00
Jimmy Shih e4da671cbc Improve the buttons layout on the "Send to Google" dialog.
For style,
I sort the attributes alphabetically except for "android:id".
If "android:id" exists, I place it on the same line as the element.
2011-09-14 17:31:57 -07:00
Josh Kasten 8cde2420e7 Merge from main repo 2011-09-13 22:48:50 -05:00
ase69s 3c4c215931 Review issues corrected 2011-09-13 20:26:28 +02:00
Rodrigo Damazio eb6cdd0fc0 Fixing the fixes for localization and track importing.
Fixes issue 462
Fixes issue 559
Fixes issue 540
2011-09-11 08:16:58 -03:00
Josh Kasten 0b1c24e55c Fixed the first app start message of "Units of Measure Notice" to jump to the
display section of the settings instead of the root when pressing yes.
2011-09-11 02:00:42 -05:00
ase69s 057c2d69a9 Suggested corrections made (Pending corrections in halt until getting response from the last revision comments i made) 2011-09-10 11:31:22 +02:00
van.isacker.pieter 79c2cf90cf Fix gpx import thread error (issue 559) 2011-09-09 16:51:08 +02:00
van.isacker.pieter 20cbdccbf6 Make use of HTTPS urls for google services.
Fix formatting by using locale settings
2011-09-09 14:49:31 +02:00
ase69s 5d05d71210 Track Color Mode Beta 2011-09-06 23:52:51 +02:00
Sandor Dornbush 08294e8ada Avoid RejectedExecutionException. 2011-09-05 21:37:09 -07:00
Sandor Dornbush 41f7262280 Merging extra head. 2011-09-02 19:27:46 -07:00
Sandor Dornbush 6aa361b801 Merging stride changes 2011-09-02 19:26:06 -07:00
Dominik R?ttsches 9281e0adf3 Revision comments for rev 5bcaab17e76f addressed. 2011-08-27 14:32:37 +03:00
Dominik R?ttsches ace10fc511 Review comments for revision fd70af575b77 addressed. 2011-08-26 18:03:57 +03:00
Dominik R?ttsches 0ac923e104 already merged 2011-08-25 15:56:16 +03:00
Dominik R?ttsches 082aa0a34a overriden in ca3651f10063 2011-08-25 15:54:48 +03:00
Dominik R?ttsches acf5c0931f Addressing review comments for StrideReadings class and related test cases:
- removing inner class, and recording of heartbeat timestamps
- fixing issue with rollover by replacing % operator with modulo operation
- comparing problematic firmware id against byte[] instead of String
- removing zephyr packet debug loggin
- not reassigning protobuffers Builders
- moving decision to use workaround into setCadence function
- license and javadoc author for testcase class
- proposal: using introspection to access private fields in testcase in order to determine expected availablity of values from getCadence, alternatively modify access to protected.
- modifying rollover test, hopefully more clear now.
- adding asserts during the loop in this test case.
2011-08-25 15:12:33 +03:00
Dominik R?ttsches ee204f35ef Addressing review comments for revision ca3651f10063.
+ limiting RunCadence to 254, same as it was done for cycling Cadence.
2011-08-25 13:16:02 +03:00
Dominik R?ttsches e885224855 TCX Output Tuning
- Interpret cadence only as biking cadence when activity was marked as biking.
- Otherwise encode as RunCadence in the Extensions
2011-08-21 16:10:01 +03:00
Dominik R?ttsches d48eccc0e4 TCX Output Tuning
- Interpret cadence only as biking cadence when activity was marked as biking.
- Otherwise encode as RunCadence in the Extensions
2011-08-21 16:08:26 +03:00
Dominik R?ttsches 21012b305f Adding battery level to sensor state activity. 2011-08-21 15:00:11 +03:00
Dominik R?ttsches a79e322eff Addressing warning. 2011-08-21 14:17:54 +03:00
Dominik R?ttsches b5a1cf1b10 Adding tests for new StrideReadings class. 2011-08-21 14:11:35 +03:00
Dominik R?ttsches 40ebd7bc2c Adressing a round of review comments. Tests in separate commit. 2011-08-21 13:55:11 +03:00
Dominik R?ttsches ef37ee0f38 Merging batteryLevel branch. 2011-08-21 00:40:47 +03:00
Dominik R?ttsches 49033bf952 Fix for Cadence values from HxM with Hardware/Firmware Identification 0x1A00316550003162 2011-08-20 16:58:36 +03:00
Dominik R?ttsches d9725240a6 Storing battery level in new protobuf field. 2011-08-20 01:25:58 +03:00
Dominik R?ttsches cb5518a261 Adding battery_level to protobuffers spec. 2011-08-20 01:01:43 +03:00
Dominik R?ttsches ac71d8aa2a Fixing TCX generation:
- Cadence before Extensions
- Adding BuildMajor and BuildMinor to Version since it seems to be required by http://connect.garmin.com
- Version Values now only contain integers by splitting mytracks version code at dots, allows correct XML validation.
2011-08-19 23:01:55 +03:00
Rodrigo Damazio 9f5fa880d6 Closing branch v1.0.20-bugfix 2010-11-26 15:01:06 -08:00
107 changed files with 3247 additions and 795 deletions
+2
View File
@@ -7,3 +7,5 @@ syntax:glob
MyTracksTest/annotations.log
**/*.pyc
**/*.pyo
syntax: glob
MyTracksTest/hs_err_pid*.log
+9 -1
View File
@@ -3,8 +3,16 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
<classpathentry kind="lib" path="libs/google-common.jar"/>
<classpathentry kind="lib" path="libs/google-api-client-1.2.2-alpha.jar"/>
<classpathentry kind="lib" path="libs/libGoogleAnalytics.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/commons-codec-1.3.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/commons-logging-1.1.1.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/google-api-client-1.5.0-beta.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/google-http-client-1.5.0-beta.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/google-oauth-client-1.5.0-beta.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/gson-1.6.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/guava-r09.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/jackson-core-asl-1.6.7.jar"/>
<classpathentry kind="lib" path="libs/google-api-client/jsr305-1.3.9.jar"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
+41 -41
View File
@@ -1,41 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MyTracks</name>
<comment></comment>
<projects>
<project>MyTracksLib</project>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<linkedResources>
<link>
<name>MyTracksLib_src</name>
<type>2</type>
<locationURI>_android_MyTracksLib_e2802071/src</locationURI>
</link>
</linkedResources>
</projectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MyTracks</name>
<comment></comment>
<projects>
<project>MyTracksLib</project>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<linkedResources>
<link>
<name>MyTracksLib_src</name>
<type>2</type>
<locationURI>_android_MyTracksLib_e2802071/src</locationURI>
</link>
</linkedResources>
</projectDescription>
+26 -5
View File
@@ -17,10 +17,11 @@ 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="31"
android:versionName="1.1.8">
android:versionCode="33"
android:versionName="1.1.10">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:name="com.google.android.apps.mytracks.MyTracksApplication"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
<uses-library android:name="com.google.android.maps" />
@@ -79,6 +80,17 @@ limitations under the License.
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.DeleteTrack"
android:label="@string/deletetrack_title"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.DELETE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
android:label="@string/waypointdetails_title">
<intent-filter>
@@ -166,6 +178,9 @@ limitations under the License.
android:exported="true"
android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<service android:name="com.google.android.apps.mytracks.services.ControlRecordingService"
android:exported="false" />
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -177,7 +192,6 @@ limitations under the License.
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.google.android.apps.mytracks.TRACK_STARTED" />
<action android:name="com.google.android.apps.mytracks.TRACK_STOPPED" />
<action android:name="com.google.android.apps.mytracks.TRACK_UPDATED" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/track_widget_info" />
@@ -219,7 +233,7 @@ limitations under the License.
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/notification_permission_label"
android:description="@string/notification_permission_description"
android:protectionLevel="normal" />
android:protectionLevel="dangerous" />
<!-- Permission to read raw track data (points, etc.) -->
<permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA"
android:permissionGroup="android.permission-group.LOCATION"
@@ -233,10 +247,17 @@ limitations under the License.
android:description="@string/write_tracks_permission_description"
android:protectionLevel="dangerous" />
<!-- Permission to get MyTracks broadcasts. For internal MyTracks communication. -->
<permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS"
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/mytracks_notifications_permission_label"
android:description="@string/mytracks_notifications_permission_description"
android:protectionLevel="signature" />
<!-- We read and write raw track data, obviously. -->
<uses-permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.TRACK_NOTIFICATIONS" />
<uses-permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS" />
<!-- Features required -->
<uses-feature android:name="android.hardware.location" android:required="true"/>
+2 -4
View File
@@ -13,7 +13,5 @@ split.density=false
target=Google Inc.:Google APIs:13
android.library.reference.1=../MyTracksLib/
# ProGuard is turned off while we investigate the removal of code that
# isn't actually dead from Fusion Tables. See issue 303 for an example of
# the result of that removal.
#proguard.config=proguard.cfg
# Enable proguard.
proguard.config=proguard.cfg
+15 -7
View File
@@ -3,12 +3,20 @@ own licenses, which allow their usage for the My Tracks application. Any other
applications that wish to use these files should consult the appropriate
licenses or the JAR's authors.
References are provided to the libraries:
google-api-client/
- see http://code.google.com/p/google-api-java-client/.
- contains Google API client library, google-api-client-1.5.0-beta.jar, and its
dependencies.
- google-api-client-1.5.0-beta.jar depends on protobuf-java-2.2.0.jar, however,
MyTracksLib already includes protobuf-java.2.3.0-lite.jar. Thus using the
newer version, protobuf-java.2.3.0-lite.jar.
- google-api-client-1.5.0-beta.jar also depends on xpp3-1.1.4c.jar, however,
some of its classes are already included in the android framework, thus not
including xpp3-1.1.4c.jar.
google-api-client-*.jar (GData client):
http://code.google.com/p/google-api-java-client/
google-common.jar
google-common.jar,
googleloginclient-helper.jar,
libmymaps.jar (Google My Maps upload library):
Licensed exclusively for use in My Tracks and derived Android applications.
googleloginclient-helper.jar
libGoogleAnalytics.jar
- see http://code.google.com/mobile/analytics/docs/.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
View File
@@ -14,6 +14,10 @@
-dontwarn com.google.android.gsf.**
-dontwarn org.codehaus.**
-dontwarn com.google.appengine.**
-dontwarn org.apache.avalon.framework.logger.**
-dontwarn org.apache.log.**
-dontwarn org.apache.log4j.**
-dontwarn javax.servlet.**
# Make our stack traces useful
# Line numbers will be correct, file names will be replaced by "MT" since the
@@ -51,3 +55,15 @@
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Needed by google-api-client to keep generic types and @Key annotations accessed via reflection
-keepclassmembers class * {
@com.google.api.client.util.Key <fields>;
}
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
# Needed by Guava
-dontwarn sun.misc.Unsafe
+9 -3
View File
@@ -40,7 +40,9 @@ limitations under the License.
android:singleLine="true"
android:autoText="true"
android:capitalize="words"
android:selectAllOnFocus="true"/>
android:selectAllOnFocus="true"
android:hint="@string/trackdetails_name_hint"
android:imeOptions="actionNext"/>
</LinearLayout>
<TextView android:text="@string/trackdetails_category"
android:layout_width="fill_parent"
@@ -48,7 +50,9 @@ limitations under the License.
<AutoCompleteTextView android:id="@+id/trackdetails_category"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"/>
android:singleLine="true"
android:hint="@string/trackdetails_category_hint"
android:imeOptions="actionNext"/>
<TextView android:text="@string/trackdetails_description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
@@ -61,7 +65,9 @@ limitations under the License.
android:selectAllOnFocus="true"
android:maxLines="50"
android:gravity="top"
android:layout_weight="1"/>
android:layout_weight="1"
android:hint="@string/trackdetails_description_hint"
android:imeOptions="actionDone"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
+13 -15
View File
@@ -127,26 +127,24 @@ limitations under the License.
</TableLayout>
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_bar"
android:padding="3dip">
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:paddingLeft="2dip"
android:paddingRight="2dip"
android:paddingTop="4dip">
<!-- left spacer -->
<LinearLayout style="@style/DialogButtonSpacer"/>
<Button android:id="@+id/sendtogoogle_cancel"
android:layout_width="wrap_content"
android:layout_height="48dip"
android:layout_gravity="right"
android:layout_gravity="left"
android:text="@string/cancel"
android:textSize="14dip"/>
<TextView
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"/>
style="@style/DialogButton"/>
<Button android:id="@+id/sendtogoogle_send_now"
android:layout_width="wrap_content"
android:layout_height="48dip"
android:layout_gravity="right"
android:text="@string/sendtogoogle_send_now"
android:textSize="14dip"/>
style="@style/DialogButton"/>
<!-- right spacer -->
<LinearLayout style="@style/DialogButtonSpacer"/>
</LinearLayout>
</LinearLayout>
@@ -50,7 +50,9 @@ limitations under the License.
android:singleLine="true"
android:autoText="true"
android:capitalize="words"
android:selectAllOnFocus="true"/>
android:selectAllOnFocus="true"
android:hint="@string/waypointdetails_name_hint"
android:imeOptions="actionNext"/>
</LinearLayout>
<LinearLayout android:id="@+id/waypointdetails_description_layout"
android:orientation="vertical"
@@ -65,7 +67,9 @@ limitations under the License.
<AutoCompleteTextView android:id="@+id/waypointdetails_category"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"/>
android:singleLine="true"
android:hint="@string/waypointdetails_category_hint"
android:imeOptions="actionNext"/>
<TextView android:text="@string/waypointdetails_description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
@@ -79,7 +83,9 @@ limitations under the License.
android:minLines="5"
android:maxLines="50"
android:gravity="top"
android:layout_weight="1"/>
android:layout_weight="1"
android:hint="@string/waypointdetails_description_hint"
android:imeOptions="actionDone"/>
</LinearLayout>
<TableLayout android:id="@+id/waypoint_stats"
+21
View File
@@ -125,6 +125,27 @@ limitations under the License.
android:text="@string/bpm" />
</TableRow>
<!-- Battery Level -->
<TableRow
android:padding="1dip">
<TextView
android:layout_column="1"
android:layout_span="3"
android:text="@string/battery_level" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
style="@style/StatsRegister.Big"
android:id="@+id/battery_level_register"
android:layout_column="1"
android:layout_span="4" />
<TextView
style="@style/StatsUnitSolo"
android:layout_column="5"
android:text="@string/percent" />
</TableRow>
</TableLayout>
</LinearLayout>
</ScrollView>
+6
View File
@@ -152,14 +152,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Detalls de la ruta</string>
<string name="trackdetails_name">Nom de la ruta</string>
<string name="trackdetails_name_hint">Nom de la ruta</string>
<string name="trackdetails_description">Descripció de la ruta</string>
<string name="trackdetails_description_hint">Descripció de la ruta</string>
<string name="trackdetails_category">Tipus d\'activitat (p.e. senderisme, bicicleta, còrrer, escalada...)</string>
<string name="trackdetails_category_hint">Tipus d\'activitat</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Detalls del marcador</string>
<string name="waypointdetails_name">Nom del marcador</string>
<string name="waypointdetails_name_hint">Nom del marcador</string>
<string name="waypointdetails_description">Descripció del marcador</string>
<string name="waypointdetails_description_hint">Descripció del marcador</string>
<string name="waypointdetails_category">Tipus de marcador (e.g. edifici, sot, vista...)</string>
<string name="waypointdetails_category_hint">Tipus de marcador</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Paràmetres de My Tracks</string>
+7
View File
@@ -154,14 +154,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Trackdetails</string>
<string name="trackdetails_name">Name</string>
<string name="trackdetails_name_hint">Name</string>
<string name="trackdetails_description">Beschreibung</string>
<string name="trackdetails_description_hint">Beschreibung</string>
<string name="trackdetails_category">Aktivität (z. B. Wandern, Fahrradfahren, Laufen, Klettern…)</string>
<string name="trackdetails_category_hint">Aktivität</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Wegpunktdetails</string>
<string name="waypointdetails_name">Wegpunktname</string>
<string name="waypointdetails_name_hint">Wegpunktname</string>
<string name="waypointdetails_description">Wegpunktbeschreibung</string>
<string name="waypointdetails_description_hint">Wegpunktbeschreibung</string>
<string name="waypointdetails_category">Art des Wegpunktes (z. B. Gebäude, Schlagloch, Sehenswürdigkeit…)</string>
<string name="waypointdetails_category_hint">Art des Wegpunktes</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Einstellungen für My Tracks</string>
@@ -333,6 +339,7 @@ limitations under the License.
<!--string name="revolutions_per_minute">rpm</string-->
<!--string name="bpm">bpm</string-->
<string name="battery_level">Akkukapazität</string>
<!-- Stats Labels -->
<string name="moving_time_label">Zeit in Bewegung</string>
+6
View File
@@ -138,14 +138,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Detalles del recorrido</string>
<string name="trackdetails_name">Nombre del recorrido</string>
<string name="trackdetails_name_hint">Nombre del recorrido</string>
<string name="trackdetails_description">Descripción del recorrido</string>
<string name="trackdetails_description_hint">Descripción del recorrido</string>
<string name="trackdetails_category">Tipo de actividad (por ejemplo, ciclismo, alpinismo...)</string>
<string name="trackdetails_category_hint">Tipo de actividad</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Detalles de señal</string>
<string name="waypointdetails_name">Nombre de señal</string>
<string name="waypointdetails_name_hint">Nombre de señal</string>
<string name="waypointdetails_description">Descripción de señal</string>
<string name="waypointdetails_description_hint">Descripción de señal</string>
<string name="waypointdetails_category">Tipo de señal (por ejemplo, edificio, bache, atracción...)</string>
<string name="waypointdetails_category_hint">Tipo de señal</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Configuraciones de My Tracks</string>
+7 -1
View File
@@ -183,14 +183,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Reitin tiedot</string>
<string name="trackdetails_name">Reitin nimi</string>
<string name="trackdetails_name_hint">Reitin nimi</string>
<string name="trackdetails_description">Reitin kuvaus</string>
<string name="trackdetails_description_hint">Reitin kuvaus</string>
<string name="trackdetails_category">Liikunnan muoto (esim. hölkkä, pyöräily, juoksu...)</string>
<string name="trackdetails_category_hint">Liikunnan muoto</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Merkinnän tiedot</string>
<string name="waypointdetails_name">Merkinnän nimi</string>
<string name="waypointdetails_name_hint">Merkinnän nimi</string>
<string name="waypointdetails_description">Merkinnän kuvaus</string>
<string name="waypointdetails_description_hint">Merkinnän kuvaus</string>
<string name="waypointdetails_category">Merkinnän tyyppi (esim. rakennus, kuoppa, nähtävyys...)</string>
<string name="waypointdetails_category_hint">Merkinnän tyyppi</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks Asetukset</string>
@@ -380,9 +386,9 @@ limitations under the License.
<!-- Sensors -->
<string name="watts">W</string>
<string name="revolutions_per_minute">rpm</string>
<string name="bpm">bpm</string>
<string name="battery_level">Akku</string>
<!-- Stats Labels -->
<string name="moving_time_label">Liikkumisaika</string>
+6
View File
@@ -144,14 +144,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Détails parcours</string>
<string name="trackdetails_name">Nom du parcours</string>
<string name="trackdetails_name_hint">Nom du parcours</string>
<string name="trackdetails_description">Description du parcours</string>
<string name="trackdetails_description_hint">Description du parcours</string>
<string name="trackdetails_category">Type d\'activité (randonnée, cyclisme, jogging, escalade...)</string>
<string name="trackdetails_category_hint">Type d\'activité</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Details balise</string>
<string name="waypointdetails_name">Nom de la balise</string>
<string name="waypointdetails_name_hint">Nom de la balise</string>
<string name="waypointdetails_description">Description balise</string>
<string name="waypointdetails_description_hint">Description balise</string>
<string name="waypointdetails_category">Nom de balise (par ex.: immeuble, boutique, point de vue...)</string>
<string name="waypointdetails_category_hint">Nom de balise</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Paramètres My Tracks</string>
+6
View File
@@ -147,14 +147,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Dettagli del percorso</string>
<string name="trackdetails_name">Nome del percorso</string>
<string name="trackdetails_name_hint">Nome del percorso</string>
<string name="trackdetails_description">Descrizione del percorso</string>
<string name="trackdetails_description_hint">Descrizione del percorso</string>
<string name="trackdetails_category">Tipo di attività (ad es. escursionismo, ciclismo, corsa, arrampicata...)</string>
<string name="trackdetails_category_hint">Tipo di attività</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Dettagli sul segnaposto</string>
<string name="waypointdetails_name">Nome del segnaposto</string>
<string name="waypointdetails_name_hint">Nome del segnaposto</string>
<string name="waypointdetails_description">Descrizione del segnaposto</string>
<string name="waypointdetails_description_hint">Descrizione del segnaposto</string>
<string name="waypointdetails_category">Tipo di segnaposto (ad es. edificio, buca, vista...)</string>
<string name="waypointdetails_category_hint">Tipo di segnaposto</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Impostazioni di My Tracks</string>
+6
View File
@@ -176,14 +176,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">トラック詳細</string>
<string name="trackdetails_name">トラック名</string>
<string name="trackdetails_name_hint">トラック名</string>
<string name="trackdetails_description">トラック説明</string>
<string name="trackdetails_description_hint">トラック説明</string>
<string name="trackdetails_category">活動タイプ(ハイキング, サイクリング、ランニング、登山...)</string>
<string name="trackdetails_category_hint">活動タイプ</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">マーカー詳細</string>
<string name="waypointdetails_name">マーカー名</string>
<string name="waypointdetails_name_hint">マーカー名</string>
<string name="waypointdetails_description">マーカー説明</string>
<string name="waypointdetails_description_hint">マーカー説明</string>
<string name="waypointdetails_category">マーカータイプ(建物、洞穴、名所...)</string>
<string name="waypointdetails_category_hint">マーカータイプ</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks設定</string>
+6
View File
@@ -149,14 +149,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Szczegóły trasy</string>
<string name="trackdetails_name">Nazwa trasy</string>
<string name="trackdetails_name_hint">Nazwa trasy</string>
<string name="trackdetails_description">Opis trasy</string>
<string name="trackdetails_description_hint">Opis trasy</string>
<string name="trackdetails_category">Typ aktywności (np. wspinanie, jazda rowerem, bieganie...)</string>
<string name="trackdetails_category_hint">Typ aktywności</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Szczegóły znacznika</string>
<string name="waypointdetails_name">Nazwa znacznika</string>
<string name="waypointdetails_name_hint">Nazwa znacznika</string>
<string name="waypointdetails_description">Opis znacznika</string>
<string name="waypointdetails_description_hint">Opis znacznika</string>
<string name="waypointdetails_category">Typ znacznika (np. budynek, jaskinia, punkt widokowy...)</string>
<string name="waypointdetails_category_hint">Typ znacznika</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Ustawienia My Tracks</string>
+6
View File
@@ -144,14 +144,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Detalhes do Caminho</string>
<string name="trackdetails_name">Nome do Caminho</string>
<string name="trackdetails_name_hint">Nome do Caminho</string>
<string name="trackdetails_description">Descrição do Caminho</string>
<string name="trackdetails_description_hint">Descrição do Caminho</string>
<string name="trackdetails_category">Tipo de Atividade (por ex. caminhada, ciclismo, escalada...)</string>
<string name="trackdetails_category_hint">Tipo de Atividade</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Detalhes do Marcador</string>
<string name="waypointdetails_name">Nome do Marcador</string>
<string name="waypointdetails_name_hint">Nome do Marcador</string>
<string name="waypointdetails_description">Descrição do Marcador</string>
<string name="waypointdetails_description_hint">Descrição do Marcador</string>
<string name="waypointdetails_category">Tipo do Marcador (por ex. Prédio, Mirante, Cachoeira...)</string>
<string name="waypointdetails_category_hint">Tipo do Marcador</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Configurações do My Tracks</string>
+6
View File
@@ -179,14 +179,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Трек детали</string>
<string name="trackdetails_name">Название трека</string>
<string name="trackdetails_name_hint">Название трека</string>
<string name="trackdetails_description">Описание трека</string>
<string name="trackdetails_description_hint">Описание трека</string>
<string name="trackdetails_category">Тип деятельности (например: пешая прогулка, велосипедная прогулка, бег, восхождение...)</string>
<string name="trackdetails_category_hint">Тип деятельности</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Маркер детали</string>
<string name="waypointdetails_name">Имя маркера</string>
<string name="waypointdetails_name_hint">Имя маркера</string>
<string name="waypointdetails_description">Описание маркера</string>
<string name="waypointdetails_description_hint">Описание маркера</string>
<string name="waypointdetails_category">Тип маркера (например: здание, выбоина, достопримечательность...)</string>
<string name="waypointdetails_category_hint">Тип маркера</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">Настройки My Tracks</string>
+6
View File
@@ -149,14 +149,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Spårdetaljer</string>
<string name="trackdetails_name">Spårnamn</string>
<string name="trackdetails_name_hint">Spårnamn</string>
<string name="trackdetails_description">Spårbeskrivning</string>
<string name="trackdetails_description_hint">Spårbeskrivning</string>
<string name="trackdetails_category">Aktivitetstyp (t ex vandring, cyckling, löpning, klättring...)</string>
<string name="trackdetails_category_hint">Aktivitetstyp</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Markördetaljer</string>
<string name="waypointdetails_name">Markörnamn</string>
<string name="waypointdetails_name_hint">Markörnamn</string>
<string name="waypointdetails_description">Markörbeskrivning</string>
<string name="waypointdetails_description_hint">Markörbeskrivning</string>
<string name="waypointdetails_category">Markörtyp (e.g. byggnad, grop, sevärdhet...)</string>
<string name="waypointdetails_category_hint">Markörtyp</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks inställningar</string>
+6
View File
@@ -180,14 +180,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">轨迹详细信息</string>
<string name="trackdetails_name">轨迹名称</string>
<string name="trackdetails_name_hint">轨迹名称</string>
<string name="trackdetails_description">轨迹描述</string>
<string name="trackdetails_description_hint">轨迹描述</string>
<string name="trackdetails_category">活动类型 (例如: 徒步, 骑行, 跑步, 攀岩...)</string>
<string name="trackdetails_category_hint">活动类型</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">标记详细信息</string>
<string name="waypointdetails_name">标记名称</string>
<string name="waypointdetails_name_hint">标记名称</string>
<string name="waypointdetails_description">标记描述</string>
<string name="waypointdetails_description_hint">标记描述</string>
<string name="waypointdetails_category">标记类型 (例如: 建筑, 陷坑, 景点...)</string>
<string name="waypointdetails_category_hint">标记类型</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks 设置</string>
@@ -118,4 +118,16 @@ limitations under the License.
<item>@string/ant_sensor_type</item>
<item>@string/srm_ant_bridge_sensor_type</item>
</string-array>
<string-array name="track_color_mode_options">
<item>@string/track_color_mode_none</item>
<item>@string/track_color_mode_fixed</item>
<item>@string/track_color_mode_dynamic</item>
</string-array>
<string-array name="track_color_mode_values">
<item>@string/none</item>
<item>@string/track_color_mode_fixed</item>
<item>@string/track_color_mode_dynamic</item>
</string-array>
</resources>
+10
View File
@@ -51,4 +51,14 @@ limitations under the License.
<!-- AppWidget colors -->
<color name="appwidget_text">#EDEDED</color>
<color name="appwidget_hr">#99999999</color>
<!-- Colors for dynamic track drawing -->
<!-- Green -->
<color name="slow_path">#F774DF00</color>
<!-- Blue -->
<color name="normal_path">#F75858FA</color>
<!-- Red -->
<color name="fast_path">#F7DF0101</color>
</resources>
+11
View File
@@ -43,7 +43,18 @@ limitations under the License.
<!-- ID for Google Analytics -->
<string name="google_analytics_id">UA-7222692-2</string>
<!-- Symbols -->
<string name="percent">%</string>
<!-- Intent actions -->
<string name="save_intent_action">android.intent.action.SAVE</string>
<string name="open_settings_screen">android.intent.action.settings.OPEN</string>
<string name="start_new_track_action">com.google.android.apps.mytracks.START_NEW_TRACK</string>
<string name="end_current_track_action">com.google.android.apps.mytracks.END_CURRENT_TRACK
</string>
<!-- Permissions -->
<!-- Permission for MyTracks notifications. -->
<string name="mytracks_notifications_permission">com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS</string>
</resources>
+22 -9
View File
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Keys for application preferences - DO NOT TRANSLATE -->
<!-- Keys for application preferences. Sorted alphabetically. - DO NOT TRANSLATE -->
<string name="allow_access_key">allowAccess</string>
<string name="announcement_frequency_key">announcementFrequency</string>
<string name="ant_heart_rate_sensor_id_key">antHeartRateSensorId</string>
<string name="ant_power_sensor_id_key">antPowerSensorId</string>
@@ -35,22 +36,34 @@ limitations under the License.
<string name="report_speed_key">reportSpeed</string>
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
<string name="send_to_docs_key">sendToDocs</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="send_to_fusion_tables_key">sendToFusionTables</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="sensor_type_key">sensorType</string>
<string name="share_url_only_key">shareUrlOnly</string>
<string name="split_frequency_key">splitFrequency</string>
<string name="timestamp_track_name_key">timestampTrackName</string>
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
<string name="track_color_mode_dynamic_speed_variation_key">trackColorModeDynamicVariation
</string>
<string name="track_color_mode_fixed_speed_medium_display_key">trackColorModeFixedMediumDisplay
</string>
<string name="track_color_mode_fixed_speed_medium_key">trackColorModeFixedMedium</string>
<string name="track_color_mode_fixed_speed_slow_display_key">trackColorModeFixedSlowDisplay
</string>
<string name="track_color_mode_fixed_speed_slow_key">trackColorModeFixedSlow</string>
<string name="track_color_mode_key">trackColorMode</string>
<!-- Preference keys which keep transient state (not user preferences). Sorted alphabetically.
- DO NOT TRANSLATE -->
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
<string name="recording_track_key">recordingTrack</string>
<string name="selected_track_key">selectedTrack</string>
<!-- Keys for non-persisted preferences - DO NOT TRANSLATE -->
<string name="backup_to_sd_key">backupToSd</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<string name="reset_key">resetToDefaults</string>
<string name="sensor_options_key">sensorOptions</string>
<!-- Keys for non-persisted preferences. Sorted alphabetically. - DO NOT TRANSLATE -->
<string name="ant_options_key">antOptions</string>
<string name="backup_to_sd_key">backupToSd</string>
<string name="display_settings_screen_key">displaySettingsScreen</string>
<string name="reset_key">resetToDefaults</string>
<string name="restore_from_sd_key">restoreFromSd</string>
<string name="sensor_options_key">sensorOptions</string>
<string name="track_color_mode_fixed_speed_options_key">trackColorModeFixedOptions</string>
</resources>
+51 -9
View File
@@ -83,7 +83,8 @@ limitations under the License.
<string name="write_progress_message">Writing file to SD card. This may take a while.</string>
<string name="progress_message_updating_track">Updating track statistics...</string>
<!-- Used by DeleteAllDataHandler -->
<!-- Used by deletion actions -->
<string name="deletetrack_title">Delete track</string>
<string name="are_you_sure_question">Are you sure?</string>
<string name="all_data_will_be_permanently_deleted">All tracks and markers will be deleted from this phone.</string>
<string name="track_will_be_permanently_deleted">The track and its markers will be permanently deleted from this phone.</string>
@@ -183,14 +184,20 @@ limitations under the License.
<!-- MyTracks Details -->
<string name="trackdetails_title">Track Details</string>
<string name="trackdetails_name">Track Name</string>
<string name="trackdetails_name_hint">Track Name</string>
<string name="trackdetails_description">Track Description</string>
<string name="trackdetails_description_hint">Track Description</string>
<string name="trackdetails_category">Activity Type (e.g. hiking, cycling, running, climbing...)</string>
<string name="trackdetails_category_hint">Activity Type</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Marker Details</string>
<string name="waypointdetails_name">Marker Name</string>
<string name="waypointdetails_name_hint">Marker Name</string>
<string name="waypointdetails_description">Marker Description</string>
<string name="waypointdetails_description_hint">Marker Description</string>
<string name="waypointdetails_category">Marker Type (e.g. Building, Pothole, Sight...)</string>
<string name="waypointdetails_category_hint">Marker Type</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks Settings</string>
@@ -201,6 +208,15 @@ limitations under the License.
<string name="settings_not_available_summary">Not available in your version of Android</string>
<string name="settings_not_while_recording">Not available while recording a track</string>
<string name="settings_allow_access">Allow access</string>
<string name="settings_allow_access_summary_on">Other apps can read, write, start/stop recording
MyTracks data.</string>
<string name="settings_allow_access_summary_off">Other apps cannot read, write, start/stop
recording MyTracks data.</string>
<string name="settings_allow_access_dialog_message">Are you sure you want to allow other apps,
including future apps, to control and access MyTracks data? There will be no further
notification.</string>
<string name="settings_announcement_frequency">Announcement frequency</string>
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string>
@@ -261,6 +277,17 @@ limitations under the License.
<string name="bluetooth_sensor_summary">The bluetooth sensor to connect to</string>
<string name="settings_modular_stats_display_title">Modular Stats Display</string>
<string name="settings_track_color_mode">Track Color Mode</string>
<string name="settings_track_color_mode_summary">The way to color the track</string>
<string name="settings_track_color_mode_fixed">Fixed</string>
<string name="settings_track_color_mode_dynamic">Dynamic</string>
<string name="settings_track_color_mode_fixed_speed_slow">Fixed Speed Slow</string>
<string name="settings_track_color_mode_fixed_speed_slow_summary">Slow speed top value</string>
<string name="settings_track_color_mode_fixed_speed_medium">Fixed Speed Medium</string>
<string name="settings_track_color_mode_fixed_speed_medium_summary">Medium speed top value</string>
<string name="settings_track_color_mode_dynamic_speed_variation">Dynamic Speed Variation</string>
<string name="settings_track_color_mode_dynamic_speed_variation_summary">% Margin from average speed to consider medium speed</string>
<!-- Backups -->
<string name="settings_backup">Backup (BETA)</string>
<string name="settings_sd_backup">SD Card Backup</string>
@@ -383,6 +410,7 @@ limitations under the License.
<string name="revolutions_per_minute">rpm</string>
<string name="bpm">bpm</string>
<string name="battery_level">Battery Level</string>
<!-- Stats Labels -->
<string name="moving_time_label">Moving Time</string>
@@ -475,7 +503,10 @@ limitations under the License.
<string name="sensor_bluetooth_options_key">bluetoothSensorsEnabled</string>
<string name="ant_options">ANT options</string>
<string name="sensor_options">Sensor Options</string>
<string name="track_color_mode">Track Color Mode</string>
<string name="fixed_speed_options">Fixed Speed Options</string>
<string name="dynamic_speed_options">Dynamic Speed Options</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">Connected to %s</string>
<string name="bluetooth_connecting">Bluetooth sensor connecting ...</string>
@@ -495,12 +526,19 @@ limitations under the License.
<string name="sending">Sending</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">receive track recording notifications</string>
<string name="read_tracks_permission_label">read recorded track data</string>
<string name="write_tracks_permission_label">write recorded track data, start/stop recording tracks</string>
<string name="notification_permission_description">Be notified when a track starts or stops being recorded.</string>
<string name="read_tracks_permission_description">Be able to read track data, such as points and waypoints.</string>
<string name="write_tracks_permission_description">Be able to write track data, such as starting recording or adding waypoints.</string>
<string name="notification_permission_label">receive MyTracks notifications</string>
<string name="mytracks_notifications_permission_label">receive MyTracks notifications</string>
<string name="read_tracks_permission_label">read MyTracks data</string>
<string name="write_tracks_permission_label">write data to MyTracks and start/stop MyTracks
recording</string>
<string name="notification_permission_description">Allows the application to receive notifications
when MyTracks starts or stops a recording.</string>
<string name="mytracks_notifications_permission_description">receive MyTracks notifications
</string>
<string name="read_tracks_permission_description">Allows the application to read MyTracks data.
</string>
<string name="write_tracks_permission_description">Allows the application to write data to
MyTracks and start/stop MyTracks recording.</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">NONE</string>
@@ -508,5 +546,9 @@ limitations under the License.
<string name="ant_bridge_sensor_option">SRM ANT+ Bridge</string>
<string name="polar_bt_sensor_option">Polar Bluetooth</string>
<string name="zephyr_bt_sensor_option">Zephyr Bluetooth</string>
<!-- Track Color Modes -->
<string name="track_color_mode_none">None</string>
<string name="track_color_mode_fixed">Fixed (BETA)</string>
<string name="track_color_mode_dynamic">Dynamic (BETA)</string>
</resources>
+18
View File
@@ -85,4 +85,22 @@ limitations under the License.
<style name="WidgetText.Register">
<item name="android:width">160px</item>
</style>
<!-- The style for the buttons on the bottom of a dialog. -->
<style name="DialogButton">
<item name="android:layout_height">48dip</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dip</item>
<item name="android:maxLines">2</item>
<item name="android:textSize">14dip</item>
</style>
<!-- The style for the spacer on the left and right of the dialog buttons. -->
<style name="DialogButtonSpacer">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">0.25</item>
<item name="android:layout_width">0dip</item>
<item name="android:orientation">horizontal</item>
<item name="android:visibility">gone</item>
</style>
</resources>
+61 -1
View File
@@ -18,7 +18,8 @@ limitations under the License.
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Display settings -->
<PreferenceScreen android:title="@string/settings_display">
<PreferenceScreen android:title="@string/settings_display"
android:key="@string/display_settings_screen_key">
<CheckBoxPreference
android:key="@string/metric_units_key"
android:title="@string/settings_metric_units"
@@ -32,6 +33,58 @@ limitations under the License.
android:summaryOn="@string/settings_report_speed_or_pace_summary_on"
android:summaryOff="@string/settings_report_speed_or_pace_summary_off"
android:defaultValue="true" />
<ListPreference android:key="@string/track_color_mode_key"
android:title="@string/settings_track_color_mode"
android:summary="@string/settings_track_color_mode_summary"
android:entries="@array/track_color_mode_options"
android:entryValues="@array/track_color_mode_values"
android:defaultValue="@string/none"/>
<PreferenceCategory android:title="@string/fixed_speed_options"
android:key="@string/track_color_mode_fixed_speed_options_key">
<EditTextPreference android:key="@string/track_color_mode_fixed_speed_slow_key"
android:title="@string/settings_track_color_mode_fixed_speed_slow"
android:summary="@string/settings_track_color_mode_fixed_speed_slow_summary"
android:defaultValue="9"
android:inputType="number"
android:maxLength="3">
</EditTextPreference>
<EditTextPreference android:key="@string/track_color_mode_fixed_speed_slow_display_key"
android:title="@string/settings_track_color_mode_fixed_speed_slow"
android:summary="@string/settings_track_color_mode_fixed_speed_slow_summary"
android:defaultValue="9"
android:inputType="number"
android:maxLength="3">
</EditTextPreference>
<EditTextPreference android:key="@string/track_color_mode_fixed_speed_medium_key"
android:title="@string/settings_track_color_mode_fixed_speed_medium"
android:summary="@string/settings_track_color_mode_fixed_speed_medium_summary"
android:defaultValue="15"
android:inputType="number"
android:maxLength="3">
</EditTextPreference>
<EditTextPreference android:key="@string/track_color_mode_fixed_speed_medium_display_key"
android:title="@string/settings_track_color_mode_fixed_speed_medium"
android:summary="@string/settings_track_color_mode_fixed_speed_medium_summary"
android:defaultValue="15"
android:inputType="number"
android:maxLength="3">
</EditTextPreference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/dynamic_speed_options">
<EditTextPreference android:key="@string/track_color_mode_dynamic_speed_variation_key"
android:title="@string/settings_track_color_mode_dynamic_speed_variation"
android:summary="@string/settings_track_color_mode_dynamic_speed_variation_summary"
android:defaultValue="25"
android:inputType="number"
android:maxLength="2">
</EditTextPreference>
</PreferenceCategory>
</PreferenceScreen>
<!-- Recording settings -->
@@ -156,6 +209,13 @@ limitations under the License.
android:summaryOn="@string/settings_share_url_only_summary_on"
android:summaryOff="@string/settings_share_url_only_summary_off"
android:defaultValue="false" />
<CheckBoxPreference
android:key="@string/allow_access_key"
android:title="@string/settings_allow_access"
android:summaryOn="@string/settings_allow_access_summary_on"
android:summaryOff="@string/settings_allow_access_summary_off"
android:defaultValue="false" />
</PreferenceScreen>
<!-- Backup settings -->
@@ -20,11 +20,11 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
@@ -148,7 +148,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import java.util.Locale;
/**
@@ -79,6 +80,8 @@ class CheckUnits {
private static void accept(Context context, SharedPreferences preferences) {
recordCheckPerformed(preferences);
Intent startIntent = new Intent(context, SettingsActivity.class);
startIntent.putExtra(context.getString(R.string.open_settings_screen),
context.getString(R.string.display_settings_screen_key));
context.startActivity(startIntent);
}
@@ -0,0 +1,62 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import android.graphics.Paint;
import android.graphics.Path;
/**
* Represents a colored {@code Path} to save its relative color for drawing.
* @author Vangelis S.
*/
public class ColoredPath {
private final Path path;
private final Paint pathPaint;
/**
* Constructor for a ColoredPath by color.
*/
public ColoredPath(int color) {
path = new Path();
pathPaint = new Paint();
pathPaint.setColor(color);
pathPaint.setStrokeWidth(3);
pathPaint.setStyle(Paint.Style.STROKE);
pathPaint.setAntiAlias(true);
}
/**
* Constructor for a ColoredPath by Paint.
*/
public ColoredPath(Paint paint) {
path = new Path();
pathPaint = paint;
}
/**
* @return the path
*/
public Path getPath() {
return path;
}
/**
* @return the pathPaint
*/
public Paint getPathPaint() {
return pathPaint;
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -26,7 +26,7 @@ public abstract class Constants {
* Should be used by all log statements
*/
public static final String TAG = "MyTracks";
/**
* Name of the top-level directory inside the SD card where our files will
* be read from/written to.
@@ -39,32 +39,21 @@ public abstract class Constants {
public static final int GET_LOGIN = 0;
public static final int GET_MAP = 1;
public static final int CREATE_MAP = 2;
public static final int SHOW_TRACK = 3;
public static final int ADD_LIST = 4;
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_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 = 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;
public static final int SHOW_TRACK = 2;
public static final int AUTHENTICATE_TO_MY_MAPS = 3;
public static final int AUTHENTICATE_TO_FUSION_TABLES = 4;
public static final int AUTHENTICATE_TO_DOCLIST = 5;
public static final int AUTHENTICATE_TO_TRIX = 6;
public static final int SHARE_GPX_FILE = 7;
public static final int SHARE_KML_FILE = 8;
public static final int SHARE_CSV_FILE = 9;
public static final int SHARE_TCX_FILE = 10;
public static final int SAVE_GPX_FILE = 11;
public static final int SAVE_KML_FILE = 12;
public static final int SAVE_CSV_FILE = 13;
public static final int SAVE_TCX_FILE = 14;
public static final int SHOW_WAYPOINT = 15;
public static final int WELCOME = 16;
/*
* Menu ids:
@@ -179,7 +168,7 @@ public abstract class Constants {
* The type of account that we can use for gdata uploads.
*/
public static final String ACCOUNT_TYPE = "com.google";
/**
* The name of extra intent property to indicate whether we want to resume
* a previously recorded track.
@@ -189,14 +178,6 @@ public abstract class Constants {
public static int getActionFromMenuId(int menuId) {
switch (menuId) {
case Constants.MENU_SEND_TO_GOOGLE:
return Constants.SEND_TO_GOOGLE;
case Constants.MENU_EDIT:
return Constants.EDIT_DETAILS;
case Constants.MENU_DELETE:
return Constants.DELETE_TRACK;
case Constants.MENU_SHARE_LINK:
return Constants.SHARE_LINK;
case Constants.MENU_SHARE_KML_FILE:
return Constants.SHARE_KML_FILE;
case Constants.MENU_SHARE_GPX_FILE:
@@ -213,15 +194,13 @@ public abstract class Constants {
return Constants.SAVE_CSV_FILE;
case Constants.MENU_SAVE_TCX_FILE:
return Constants.SAVE_TCX_FILE;
case Constants.MENU_CLEAR_MAP:
return Constants.CLEAR_MAP;
default:
return -1;
}
}
public static final String MAPSHOP_BASE_URL =
"http://maps.google.com/maps/ms";
"https://maps.google.com/maps/ms";
/*
* Default values - keep in sync with those in preferences.xml.
@@ -0,0 +1,114 @@
/*
* 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 static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* Activity used to delete a track.
*
* @author Rodrigo Damazio
*/
public class DeleteTrack extends Activity
implements DialogInterface.OnClickListener, OnCancelListener {
private static final int CONFIRM_DIALOG = 1;
private MyTracksProviderUtils providerUtils;
private long deleteTrackId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (!Intent.ACTION_DELETE.equals(action) ||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad delete intent: " + intent);
finish();
}
deleteTrackId = ContentUris.parseId(data);
showDialog(CONFIRM_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != CONFIRM_DIALOG) {
Log.e(TAG, "Unknown dialog " + id);
return null;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.track_will_be_permanently_deleted));
builder.setTitle(getString(R.string.are_you_sure_question));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(getString(R.string.yes), this);
builder.setNegativeButton(getString(R.string.no), this);
builder.setOnCancelListener(this);
return builder.create();
}
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialogInterface.dismiss();
if (which == DialogInterface.BUTTON_POSITIVE) {
deleteTrack();
}
finish();
}
@Override
public void onCancel(DialogInterface dialog) {
onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
private void deleteTrack() {
providerUtils.deleteTrack(deleteTrackId);
// If the track we just deleted was selected, unselect it.
String selectedKey = getString(R.string.selected_track_key);
SharedPreferences preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
Editor editor = preferences.edit().putLong(selectedKey, -1);
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
}
}
}
@@ -95,6 +95,15 @@ public class ImportAllTracks {
Log.i(Constants.TAG, "ImportAllTracks: Releasing wake lock.");
}
activity.runOnUiThread(new Thread() {
@Override
public void run() {
showDoneDialog();
}
});
}
private void showDoneDialog() {
Log.i(Constants.TAG, "ImportAllTracks: Done");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (gpxFileCount == 0) {
@@ -208,4 +217,4 @@ public class ImportAllTracks {
}
return gpxFiles;
}
}
}
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
@@ -35,8 +36,10 @@ import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.mytracks.R;
import android.content.ContentUris;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -182,7 +185,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
Log.d(TAG, "MapActivity.onResume");
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.POINT_UPDATES,
@@ -412,6 +415,23 @@ public class MapActivity extends com.google.android.maps.MapActivity
SaveActivity.handleExportTrackAction(this, dataHub.getSelectedTrackId(),
Constants.getActionFromMenuId(item.getItemId()));
return true;
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, TrackDetails.class);
// TODO: Pass in a content URI
intent.putExtra("trackid", dataHub.getSelectedTrackId());
startActivity(intent);
return true;
}
case Constants.MENU_DELETE: {
Uri uri = ContentUris.withAppendedId(
TracksColumns.CONTENT_URI, dataHub.getSelectedTrackId());
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
return true;
}
case Constants.MENU_CLEAR_MAP:
dataHub.unloadCurrentTrack();
return true;
default:
return super.onMenuItemSelected(featureId, item);
}
@@ -18,6 +18,9 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.maps.TrackPathPainter;
import com.google.android.apps.mytracks.maps.TrackPathPainterFactory;
import com.google.android.apps.mytracks.maps.TrackPathUtilities;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
@@ -27,10 +30,11 @@ import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -48,7 +52,7 @@ import java.util.concurrent.BlockingQueue;
*
* @author Leif Hendrik Wilden
*/
public class MapOverlay extends Overlay {
public class MapOverlay extends Overlay implements OnSharedPreferenceChangeListener {
private final Drawable[] arrows;
private final int arrowWidth, arrowHeight;
@@ -57,7 +61,6 @@ public class MapOverlay extends Overlay {
private final Drawable startMarker;
private final Drawable endMarker;
private final int markerWidth, markerHeight;
private final Paint selectedTrackPaint;
private final Paint errorCirclePaint;
private final Context context;
private final List<Waypoint> waypoints;
@@ -73,22 +76,26 @@ public class MapOverlay extends Overlay {
private GeoPoint lastReferencePoint;
private Rect lastViewRect;
private Path lastPath;
private boolean lastPathExists;
private Location lastLocation;
private TrackPathPainter trackPathPainter;
/**
* Represents a pre-processed {@code Location} to speed up drawing.
* This class is more like a data object and doesn't provide accessors.
*/
private static class CachedLocation {
public static class CachedLocation {
public final boolean valid;
public final GeoPoint geoPoint;
public final int speed;
/**
* Constructor for an invalid cached location.
*/
public CachedLocation() {
this.valid = false;
this.geoPoint = null;
this.speed = -1;
}
/**
@@ -97,6 +104,7 @@ public class MapOverlay extends Overlay {
public CachedLocation(Location location) {
this.valid = LocationUtils.isValidLocation(location);
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
this.speed = (int) Math.floor(location.getSpeed() * 3.6);
}
};
@@ -151,18 +159,13 @@ public class MapOverlay extends Overlay {
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
selectedTrackPaint = new Paint();
selectedTrackPaint.setColor(resources.getColor(R.color.red));
selectedTrackPaint.setStrokeWidth(3);
selectedTrackPaint.setStyle(Paint.Style.STROKE);
selectedTrackPaint.setAntiAlias(true);
errorCirclePaint = new Paint();
errorCirclePaint.setColor(resources.getColor(R.color.blue));
errorCirclePaint.setStyle(Paint.Style.STROKE);
errorCirclePaint.setStrokeWidth(3);
errorCirclePaint = TrackPathUtilities.getPaint(R.color.blue, context);
errorCirclePaint.setAlpha(127);
errorCirclePaint.setAntiAlias(true);
trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
context.getSharedPreferences(Constants.SETTINGS_NAME, 0)
.registerOnSharedPreferenceChangeListener(this);
}
/**
@@ -205,18 +208,19 @@ public class MapOverlay extends Overlay {
}
// Visible for testing.
int getNumWaypoints() {
public int getNumWaypoints() {
synchronized (waypoints) {
return waypoints.size();
}
}
public void clearPoints() {
synchronized (points) {
points.clear();
synchronized (getPoints()) {
getPoints().clear();
pendingPoints.clear();
lastPath = null;
lastPathExists = false;
lastViewRect = null;
trackPathPainter.clear();
}
}
@@ -254,6 +258,9 @@ public class MapOverlay extends Overlay {
// Draw the selected track:
drawTrack(canvas, projection, viewRect);
// Draw the "Start" and "End" markers:
drawMarkers(canvas, projection);
// Draw the waypoints:
drawWaypoints(canvas, projection);
}
@@ -262,6 +269,28 @@ public class MapOverlay extends Overlay {
drawMyLocation(canvas, projection);
}
private void drawMarkers(Canvas canvas, Projection projection) {
// Draw the "End" marker.
if (showEndMarker) {
for (int i = getPoints().size() - 1; i >= 0; --i) {
if (getPoints().get(i).valid) {
drawElement(canvas, projection, getPoints().get(i).geoPoint, endMarker,
-markerWidth / 2, -markerHeight);
break;
}
}
}
// Draw the "Start" marker.
for (int i = 0; i < getPoints().size(); ++i) {
if (getPoints().get(i).valid) {
drawElement(canvas, projection, getPoints().get(i).geoPoint, startMarker,
-markerWidth / 2, -markerHeight);
break;
}
}
}
// Visible for testing.
Projection getMapProjection(MapView mapView) {
return mapView.getProjection();
@@ -275,15 +304,15 @@ public class MapOverlay extends Overlay {
int cy = mapView.getMapCenter().getLatitudeE6();
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
}
// Visible for testing.
Path newPath() {
return new Path();
// For use in testing only.
public TrackPathPainter getTrackPathPainter() {
return trackPathPainter;
}
// Visible for testing.
Path getLastPath() {
return lastPath;
// For use in testing only.
public void setTrackPathPainter(TrackPathPainter trackPathPainter) {
this.trackPathPainter = trackPathPainter;
}
private void drawWaypoints(Canvas canvas, Projection projection) {
@@ -311,96 +340,39 @@ public class MapOverlay extends Overlay {
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
}
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
Path path;
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect)
{
boolean draw;
synchronized (points) {
// Merge the pending points with the list of cached locations.
final GeoPoint referencePoint = projection.fromPixels(0, 0);
int newPoints = pendingPoints.drainTo(points);
int newPoints = pendingPoints.drainTo(points);
boolean newProjection = !viewRect.equals(lastViewRect) ||
!referencePoint.equals(lastReferencePoint);
if (newPoints == 0 && lastPath != null && !newProjection) {
!referencePoint.equals(lastReferencePoint);
if (newPoints == 0 && lastPathExists && !newProjection) {
// No need to recreate path (same points and viewing area).
path = lastPath;
draw = true;
} else {
int numPoints = points.size();
if (numPoints < 2) {
// Not enough points to draw a path.
path = null;
} else if (lastPath != null && !newProjection) {
draw = false;
} else if (!trackPathPainter.needsRedraw() && lastPathExists && !newProjection) {
// Incremental update of the path, without repositioning the view.
path = lastPath;
updatePath(projection, viewRect, path, numPoints - newPoints);
draw = true;
trackPathPainter.updatePath(projection, viewRect, numPoints - newPoints, alwaysVisible, points);
} else {
// The view has changed so we have to start from scratch.
path = newPath();
path.incReserve(numPoints);
updatePath(projection, viewRect, path, 0);
draw = true;
trackPathPainter.updatePath(projection, viewRect, 0, alwaysVisible, points);
}
lastPath = path;
}
lastReferencePoint = referencePoint;
lastViewRect = viewRect;
}
if (path != null) {
canvas.drawPath(path, selectedTrackPaint);
}
// Draw the "End" marker.
if (showEndMarker) {
for (int i = points.size() - 1; i >= 0; --i) {
if (points.get(i).valid) {
drawElement(canvas, projection, points.get(i).geoPoint, endMarker,
-markerWidth / 2, -markerHeight);
break;
}
}
}
// Draw the "Start" marker.
for (int i = 0; i < points.size(); ++i) {
if (points.get(i).valid) {
drawElement(canvas, projection, points.get(i).geoPoint, startMarker,
-markerWidth / 2, -markerHeight);
break;
}
}
}
private void updatePath(Projection projection, Rect viewRect, Path path,
int startLocationIdx) {
// Whether to start a new segment on new valid and visible point.
boolean newSegment =
startLocationIdx > 0 ? !points.get(startLocationIdx - 1).valid : true;
boolean lastVisible = !newSegment;
final Point pt = new Point();
// Loop over track points.
for (int i = startLocationIdx; i < points.size(); ++i) {
CachedLocation loc = points.get(i);
// Check if valid, if not then indicate a new segment.
if (!loc.valid) {
newSegment = true;
continue;
}
final GeoPoint geoPoint = loc.geoPoint;
// Check if this breaks the existing segment.
boolean visible = alwaysVisible || viewRect.contains(
geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
if (!visible && !lastVisible) {
// This is a point outside view not connected to a visible one.
newSegment = true;
}
lastVisible = visible;
// Either move to beginning of a new segment or continue the old one.
projection.toPixels(geoPoint, pt);
if (newSegment) {
path.moveTo(pt.x, pt.y);
newSegment = false;
} else {
path.lineTo(pt.x, pt.y);
}
if (draw) {
trackPathPainter.drawTrack(canvas);
}
}
@@ -479,4 +451,21 @@ public class MapOverlay extends Overlay {
}
return super.onTap(p, mapView);
}
/**
* @return the points
*/
public List<CachedLocation> getPoints() {
return points;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d(TAG, "MapOverlay: onSharedPreferences changed " + key);
if (key != null) {
if (key.equals(context.getString(R.string.track_color_mode_key))) {
trackPathPainter = TrackPathPainterFactory.getTrackPathPainter(context);
}
}
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -24,7 +24,7 @@ import android.view.MenuItem;
/**
* Manage the application menus.
*
*
* @author Sandor Dornbush
*/
class MenuManager {
@@ -34,7 +34,7 @@ class MenuManager {
public MenuManager(MyTracks activity) {
this.activity = activity;
}
public boolean onCreateOptionsMenu(Menu menu) {
activity.getMenuInflater().inflate(R.menu.main, menu);
return true;
@@ -52,7 +52,7 @@ class MenuManager {
.setEnabled(isRecording)
.setVisible(isRecording);
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_start_recording: {
@@ -86,10 +86,6 @@ class MenuManager {
case R.id.menu_help: {
return startActivity(WelcomeActivity.class);
}
case Constants.MENU_CLEAR_MAP: {
activity.clearSelectedTrack();
return true;
}
}
return false;
}
@@ -33,10 +33,8 @@ import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.TabActivity;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
@@ -61,6 +59,7 @@ import android.widget.Toast;
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@SuppressWarnings("deprecation")
public class MyTracks extends TabActivity implements OnTouchListener {
private TrackDataHub dataHub;
@@ -147,7 +146,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
providerUtils = MyTracksProviderUtils.Factory.get(this);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
dataHub = TrackDataHub.newInstance(this);
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
menuManager = new MenuManager(this);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindCallback);
@@ -250,7 +249,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
protected void onDestroy() {
Log.d(TAG, "MyTracks.onDestroy");
serviceConnection.unbind();
super.onDestroy();
}
@@ -314,7 +312,7 @@ public class MyTracks extends TabActivity implements OnTouchListener {
dataHub.loadTrack(trackId);
// The track list passed the requested action as result code. Hand
// it off to the onAcitivtyResult for further processing:
// it off to the onActivityResult for further processing:
if (resultCode != Constants.SHOW_TRACK) {
onActivityResult(resultCode, Activity.RESULT_OK, results);
}
@@ -336,24 +334,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
}
break;
}
case Constants.DELETE_TRACK: {
if (results != null && resultCode == RESULT_OK) {
deleteTrack(trackId);
}
break;
}
case Constants.EDIT_DETAILS: {
if (results != null && resultCode == RESULT_OK) {
Intent intent = new Intent(this, TrackDetails.class);
intent.putExtra("trackid", trackId);
startActivity(intent);
}
break;
}
case Constants.CLEAR_MAP: {
dataHub.unloadCurrentTrack();
break;
}
case Constants.WELCOME: {
CheckUnits.check(this);
break;
@@ -373,36 +353,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
return false;
}
/**
* Deletes the track with the given id.
* Prompts the user if he want to really delete the track first.
* If the selected track is deleted, the selection will be removed.
*/
public void deleteTrack(final long trackId) {
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.track_will_be_permanently_deleted));
builder.setTitle(getString(R.string.are_you_sure_question));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(getString(R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
providerUtils.deleteTrack(trackId);
if (trackId == dataHub.getSelectedTrackId()) {
dataHub.unloadCurrentTrack();
}
}});
builder.setNegativeButton(getString(R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
dialog = builder.create();
dialog.show();
}
/**
* Inserts a waypoint marker.
*
@@ -493,10 +443,6 @@ public class MyTracks extends TabActivity implements OnTouchListener {
}
}
void clearSelectedTrack() {
dataHub.unloadCurrentTrack();
}
long getSelectedTrackId() {
return dataHub.getSelectedTrackId();
}
@@ -0,0 +1,42 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.TrackDataHub;
import android.app.Application;
/**
* MyTracksApplication for keeping global state.
*
* @author jshih@google.com (Jimmy Shih)
*/
public class MyTracksApplication extends Application {
private TrackDataHub trackDataHub;
/**
* Gets the application's TrackDataHub.
*
* Note: use synchronized to make sure only one instance is created per application.
*/
public synchronized TrackDataHub getTrackDataHub() {
if (trackDataHub == null) {
trackDataHub = TrackDataHub.newInstance(getApplicationContext());
}
return trackDataHub;
}
}
@@ -20,6 +20,8 @@ import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -55,7 +57,8 @@ public class SensorStateActivity extends Activity {
private final Runnable stateUpdater = new Runnable() {
public void run() {
updateState();
if (isVisible) // only update when UI is visible
updateState();
}
};
@@ -73,6 +76,23 @@ public class SensorStateActivity extends Activity {
runOnUiThread(stateUpdater);
}
};
/**
* A temporary sensor manager, when none is available.
*/
SensorManager tempSensorManager = null;
/**
* A state flag set to true when the activity is active/visible,
* i.e. after resume, and before pause
*
* Used to avoid updating after the pause event, because sometimes an update
* event occurs even after the timer is cancelled. In this case,
* it could cause the {@link #tempSensorManager} to be recreated, after it
* is destroyed at the pause event.
*/
boolean isVisible = false;
public SensorStateActivity() {
utils = new StatsUtilities(this);
@@ -86,7 +106,6 @@ public class SensorStateActivity extends Activity {
setContentView(R.layout.sensor_state);
// TODO: Allow reading from sensors even if service is not running.
serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
serviceConnection.bindIfRunning();
updateState();
@@ -95,6 +114,8 @@ public class SensorStateActivity extends Activity {
@Override
protected void onResume() {
super.onResume();
isVisible = true;
serviceConnection.bindIfRunning();
@@ -104,9 +125,12 @@ public class SensorStateActivity extends Activity {
@Override
protected void onPause() {
isVisible = false;
timer.cancel();
timer.purge();
timer = null;
stopTempSensorManager();
super.onPause();
}
@@ -117,35 +141,106 @@ public class SensorStateActivity extends Activity {
}
protected void updateState() {
Log.d(TAG, "Updating SensorStateActivity");
ITrackRecordingService service = serviceConnection.getServiceIfBound();
if (service == null) {
Log.d(Constants.TAG, "Could not get track recording service.");
updateSensorState(Sensor.SensorState.NONE);
updateSensorData(null);
return;
}
Sensor.SensorDataSet sds = null;
try {
byte[] buff = service.getSensorData();
if (buff != null) {
sds = Sensor.SensorDataSet.parseFrom(buff);
updateSensorData(sds);
// Check if service is available, and recording.
boolean isRecording = false;
if (service != null) {
try {
isRecording = service.isRecording();
} catch (RemoteException e) {
Log.e(TAG, "Unable to determine if service is recording.", e);
}
} catch (RemoteException e) {
Log.e(Constants.TAG, "Could not read sensor data.", e);
} catch (InvalidProtocolBufferException e) {
Log.e(Constants.TAG, "Could not read sensor data.", e);
}
updateSensorData(sds);
// If either service isn't available, or not recording.
if (!isRecording) {
updateFromTempSensorManager();
} else {
updateFromSysSensorManager();
}
}
private void updateFromTempSensorManager()
{
// Use variables to hold the sensor state and data set.
Sensor.SensorState currentState = null;
Sensor.SensorDataSet currentDataSet = null;
// If no temp sensor manager is present, create one, and start it.
if (tempSensorManager == null) {
tempSensorManager = SensorManagerFactory.getSensorManager(this);
if (tempSensorManager != null)
tempSensorManager.onStartTrack();
}
// If a temp sensor manager is available, use states from temp sensor
// manager.
if (tempSensorManager != null) {
currentState = tempSensorManager.getSensorState();
currentDataSet = tempSensorManager.getSensorDataSet();
}
try {
int i = service.getSensorState();
updateSensorState(Sensor.SensorState.valueOf(i));
} catch (RemoteException e) {
Log.e(Constants.TAG, "Could not read sensor state.", e);
updateSensorState(Sensor.SensorState.NONE);
// Update the sensor state, and sensor data, using the variables.
updateSensorStateAndData(currentState, currentDataSet);
}
private void updateFromSysSensorManager()
{
// Use variables to hold the sensor state and data set.
Sensor.SensorState currentState = null;
Sensor.SensorDataSet currentDataSet = null;
ITrackRecordingService service = serviceConnection.getServiceIfBound();
// If a temp sensor manager is present, shut it down,
// probably recording just started.
stopTempSensorManager();
// Get sensor details from the service.
if (service == null) {
Log.d(TAG, "Could not get track recording service.");
} else {
try {
byte[] buff = service.getSensorData();
if (buff != null) {
currentDataSet = Sensor.SensorDataSet.parseFrom(buff);
}
} catch (RemoteException e) {
Log.e(TAG, "Could not read sensor data.", e);
} catch (InvalidProtocolBufferException e) {
Log.e(TAG, "Could not read sensor data.", e);
}
try {
currentState = Sensor.SensorState.valueOf(service.getSensorState());
} catch (RemoteException e) {
Log.e(TAG, "Could not read sensor state.", e);
currentState = Sensor.SensorState.NONE;
}
}
// Update the sensor state, and sensor data, using the variables.
updateSensorStateAndData(currentState, currentDataSet);
}
/**
* Stops the temporary sensor manager, if one exists.
*/
private void stopTempSensorManager()
{
if (tempSensorManager != null) {
tempSensorManager.shutdown();
tempSensorManager = null;
}
}
private void updateSensorStateAndData(Sensor.SensorState state, Sensor.SensorDataSet dataSet)
{
updateSensorState(state == null ? Sensor.SensorState.NONE : state);
updateSensorData(dataSet);
}
private void updateSensorState(Sensor.SensorState state) {
@@ -160,6 +255,7 @@ public class SensorStateActivity extends Activity {
utils.setUnknown(R.id.cadence_state_register);
utils.setUnknown(R.id.power_state_register);
utils.setUnknown(R.id.heart_rate_register);
utils.setUnknown(R.id.battery_level_register);
return;
}
@@ -206,5 +302,18 @@ public class SensorStateActivity extends Activity {
: Sensor.SensorState.NONE,
this));
}
if (sds.hasBatteryLevel() && sds.getBatteryLevel().hasValue()
&& sds.getBatteryLevel().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.battery_level_register,
Integer.toString(sds.getBatteryLevel().getValue()));
} else {
utils.setText(R.id.battery_level_register,
SensorUtils.getStateAsString(
sds.hasBatteryLevel()
? sds.getBatteryLevel().getState()
: Sensor.SensorState.NONE,
this));
}
}
}
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.services.sensors.ant.AntUtils;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
@@ -32,11 +33,13 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.Settings;
@@ -50,7 +53,11 @@ import java.util.Set;
/**
* An activity that let's the user see and edit the settings.
*
*
* This activity has two entry points, "root" and "display" preference screen.
* If bundle.getString("open_settings_screen") is set to "display_settings_screen_key", then
* the "display" preference screen is shown, otherwise, the "root" display preference is shown.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@@ -58,12 +65,22 @@ public class SettingsActivity extends PreferenceActivity {
private BackupPreferencesListener backupListener;
private SharedPreferences preferences;
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
initActivityCommons();
// If we only need the display setting screen nothing else needs to load.
if (processIntent())
return;
initActivitySpecifics();
}
private void initActivityCommons() {
// The volume we want to control is the Text-To-Speech volume
ApiFeatures apiFeatures = ApiFeatures.getInstance();
int volumeStream =
@@ -83,6 +100,19 @@ public class SettingsActivity extends PreferenceActivity {
// Load the preferences to be displayed
addPreferencesFromResource(R.xml.preferences);
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
}
}
private void initActivitySpecifics() {
// Hook up switching of displayed list entries between metric and imperial
// units
CheckBoxPreference metricUnitsPreference =
@@ -101,18 +131,8 @@ public class SettingsActivity extends PreferenceActivity {
updatePreferenceUnits(metricUnitsPreference.isChecked());
customizeSensorOptionsPreferences();
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
(IntegerListPreference) findPreference(
getString(R.string.announcement_frequency_key));
announcementFrequency.setEnabled(false);
announcementFrequency.setValue("-1");
announcementFrequency.setSummary(
R.string.settings_not_available_summary);
}
customizeTrackColorModePreferences();
// Hook up action for resetting all settings
Preference resetPreference = findPreference(getString(R.string.reset_key));
resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -122,6 +142,50 @@ public class SettingsActivity extends PreferenceActivity {
return true;
}
});
// Add a confirmation dialog for the "Allow access" preference.
final CheckBoxPreference allowAccessPreference = (CheckBoxPreference) findPreference(
getString(R.string.allow_access_key));
allowAccessPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if ((Boolean) newValue) {
AlertDialog dialog = new AlertDialog.Builder(SettingsActivity.this)
.setCancelable(true)
.setTitle(getString(R.string.settings_allow_access))
.setMessage(getString(R.string.settings_allow_access_dialog_message))
.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int button) {
allowAccessPreference.setChecked(true);
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
dialog.show();
return false;
} else {
return true;
}
}
});
}
private boolean processIntent() {
boolean showDisplaySettings = false;
Bundle bundle = getIntent().getExtras();
PreferenceScreen preferenceScreen;
String intentString = getString(R.string.open_settings_screen);
if (bundle != null) {
preferenceScreen = (PreferenceScreen) findPreference(bundle.getString(intentString));
if (preferenceScreen != null) {
showDisplaySettings = true;
setPreferenceScreen(preferenceScreen);
}
}
return showDisplaySettings;
}
private void customizeSensorOptionsPreferences() {
@@ -170,10 +234,37 @@ public class SettingsActivity extends PreferenceActivity {
sensorOptionsScreen.removePreference(findPreference(getString(R.string.ant_options_key)));
}
}
private void customizeTrackColorModePreferences() {
ListPreference trackColorModePreference =
(ListPreference) findPreference(getString(R.string.track_color_mode_key));
trackColorModePreference.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
updateTrackColorModeSettings((String) newValue);
return true;
}
});
updateTrackColorModeSettings(trackColorModePreference.getValue());
setTrackColorModePreferenceListeners();
PreferenceCategory speedOptionsCategory =
(PreferenceCategory) findPreference(getString(R.string.track_color_mode_fixed_speed_options_key));
speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_slow_key)));
speedOptionsCategory.removePreference(findPreference(getString(R.string.track_color_mode_fixed_speed_medium_key)));
}
@Override
protected void onResume() {
super.onResume();
// If we only need the display setting screen nothing else needs to load.
if (processIntent())
return;
configureBluetoothPreferences();
Preference backupNowPreference =
@@ -251,6 +342,18 @@ public class SettingsActivity extends PreferenceActivity {
}
}
private void updateTrackColorModeSettings(String trackColorMode) {
boolean usesFixedSpeed = trackColorMode.equals(getString(R.string.track_color_mode_fixed));
boolean usesDynamicSpeed = trackColorMode.equals(getString(R.string.track_color_mode_dynamic));
findPreference(
getString(R.string.track_color_mode_fixed_speed_slow_display_key)).setEnabled(usesFixedSpeed);
findPreference(
getString(R.string.track_color_mode_fixed_speed_medium_display_key)).setEnabled(usesFixedSpeed);
findPreference(
getString(R.string.track_color_mode_dynamic_speed_variation_key)).setEnabled(usesDynamicSpeed);
}
/**
* Updates all the preferences which give options with distance units to use
* the proper unit the user has selected.
@@ -379,4 +482,84 @@ public class SettingsActivity extends PreferenceActivity {
}
}.start();
}
/**
* Set the given edit text preference text.
* If the units are not metric convert the value before displaying.
*/
private void viewTrackColorModeSettings(EditTextPreference preference, int id) {
CheckBoxPreference metricUnitsPreference = (CheckBoxPreference) findPreference(
getString(R.string.metric_units_key));
if(metricUnitsPreference.isChecked()) {
return;
}
// Convert miles/h to km/h
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
String metricspeed = prefs.getString(getString(id), null);
int englishspeed;
try {
englishspeed = (int) (Double.parseDouble(metricspeed) * UnitConversions.KMH_TO_MPH);
} catch (NumberFormatException e) {
englishspeed = 0;
}
preference.getEditText().setText(String.valueOf(englishspeed));
}
/**
* Saves the given edit text preference value.
* If the units are not metric convert the value before saving.
*/
private void validateTrackColorModeSettings(EditTextPreference preference,
String newValue, int id) {
CheckBoxPreference metricUnitsPreference = (CheckBoxPreference) findPreference(
getString(R.string.metric_units_key));
String metricspeed;
if(!metricUnitsPreference.isChecked()) {
// Convert miles/h to km/h
try {
metricspeed = String.valueOf((int) (Double.parseDouble(newValue) * UnitConversions.MPH_TO_KMH) + 1);
} catch (NumberFormatException e) {
metricspeed = "0";
}
} else {
metricspeed = newValue;
}
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.edit().putString(getString(id), metricspeed).commit();
}
/**
* Sets the TrackColorMode preference listeners.
*/
private void setTrackColorModePreferenceListeners() {
setTrackColorModePreferenceListener(R.string.track_color_mode_fixed_speed_slow_display_key,
R.string.track_color_mode_fixed_speed_slow_key);
setTrackColorModePreferenceListener(R.string.track_color_mode_fixed_speed_medium_display_key,
R.string.track_color_mode_fixed_speed_medium_key);
}
/**
* Sets a TrackColorMode preference listener.
*/
private void setTrackColorModePreferenceListener(int displayKey, final int metricKey) {
EditTextPreference trackColorModePreference =
(EditTextPreference) findPreference(getString(displayKey));
trackColorModePreference.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
validateTrackColorModeSettings((EditTextPreference) preference, (String) newValue, metricKey);
return true;
}
});
trackColorModePreference.setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
viewTrackColorModeSettings((EditTextPreference) preference, metricKey);
return true;
}
});
}
}
@@ -137,7 +137,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
@@ -25,11 +27,14 @@ import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
@@ -182,14 +187,16 @@ public class TrackList extends ListActivity
SaveActivity.handleExportTrackAction(this, trackId,
Constants.getActionFromMenuId(item.getItemId()));
return true;
default: {
Intent result = new Intent();
result.putExtra("trackid", trackId);
setResult(
Constants.getActionFromMenuId(item.getItemId()), result);
finish();
case Constants.MENU_DELETE: {
Intent intent = new Intent(Intent.ACTION_DELETE);
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
startActivity(intent);
return true;
}
default:
Log.w(TAG, "Unknown menu item: " + item.getItemId() + "(" + item.getTitle() + ")");
return super.onMenuItemSelected(featureId, item);
}
}
@@ -28,6 +28,7 @@ import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
@@ -139,6 +140,7 @@ public class WaypointDetails extends Activity
utils.setAllStats(waypointStats);
utils.setAltitude(
R.id.elevation_register, waypoint.getLocation().getAltitude());
name.setImeOptions(EditorInfo.IME_ACTION_DONE);
break;
}
icon.setImageDrawable(getResources().getDrawable(iconId));
@@ -96,11 +96,7 @@ public class WaypointsList extends ListActivity
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent result = new Intent();
result.putExtra("trackid", trackId);
result.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, id);
setResult(Constants.EDIT_WAYPOINT, result);
finish();
editWaypoint(id);
}
@Override
@@ -108,14 +104,15 @@ public class WaypointsList extends ListActivity
if (!super.onMenuItemSelected(featureId, item)) {
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, selectedWaypointId);
Intent result = new Intent();
result.putExtra("trackid", trackId);
result.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, selectedWaypointId);
setResult(RESULT_OK, result);
finish();
return true;
}
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, WaypointDetails.class);
intent.putExtra("trackid", trackId);
intent.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, selectedWaypointId);
startActivity(intent);
editWaypoint(selectedWaypointId);
return true;
}
case Constants.MENU_DELETE: {
@@ -126,6 +123,13 @@ public class WaypointsList extends ListActivity
return false;
}
private void editWaypoint(long waypointId) {
Intent intent = new Intent(this, WaypointDetails.class);
intent.putExtra("trackid", trackId);
intent.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, waypointId);
startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -17,11 +17,13 @@ package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
@@ -30,6 +32,8 @@ import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
@@ -183,8 +187,22 @@ public class MyTracksProvider extends ContentProvider {
"waypoints/#", WAYPOINTS_ID);
}
private boolean canAccess() {
if (Binder.getCallingPid() == Process.myPid()) {
return true;
} else {
Context context = getContext();
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(context.getString(R.string.allow_access_key), false);
}
}
@Override
public boolean onCreate() {
if (!canAccess()) {
return false;
}
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
try {
db = dbHelper.getWritableDatabase();
@@ -196,6 +214,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int delete(Uri url, String where, String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
String table;
boolean shouldVacuum = false;
switch (urlMatcher.match(url)) {
@@ -228,6 +249,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public String getType(Uri url) {
if (!canAccess()) {
return null;
}
switch (urlMatcher.match(url)) {
case TRACKPOINTS:
return TrackPointsColumns.CONTENT_TYPE;
@@ -248,6 +272,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public Uri insert(Uri url, ContentValues initialValues) {
if (!canAccess()) {
return null;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.insert");
ContentValues values;
if (initialValues != null) {
@@ -276,6 +303,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
if (!canAccess()) {
return 0;
}
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
int numInserted = 0;
try {
@@ -348,6 +378,9 @@ public class MyTracksProvider extends ContentProvider {
public Cursor query(
Uri url, String[] projection, String selection, String[] selectionArgs,
String sort) {
if (!canAccess()) {
return null;
}
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int match = urlMatcher.match(url);
String sortOrder = null;
@@ -399,6 +432,9 @@ public class MyTracksProvider extends ContentProvider {
@Override
public int update(Uri url, ContentValues values, String where,
String[] selectionArgs) {
if (!canAccess()) {
return 0;
}
int count;
int match = urlMatcher.match(url);
if (match == TRACKPOINTS) {
@@ -195,15 +195,12 @@ public class TrackDataHub {
private int lastSamplingFrequency;
private DoubleBufferedLocationFactory locationFactory;
private static TrackDataHub startedInstance;
private boolean started = false;
/**
* Builds a new {@link TrackDataHub} instance.
*/
public synchronized static TrackDataHub newInstance(Context context) {
// Ensure our singleton is never bound to an activity, to avoid memory leaks.
context = context.getApplicationContext();
SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
return new TrackDataHub(context,
@@ -240,11 +237,11 @@ public class TrackDataHub {
*/
public void start() {
Log.i(TAG, "TrackDataHub.start");
if (startedInstance != null) {
if (isStarted()) {
Log.w(TAG, "Already started, ignoring");
return;
}
startedInstance = this;
started = true;
listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start();
@@ -266,20 +263,6 @@ public class TrackDataHub {
return new DataSourcesWrapperImpl(context, preferences);
}
/**
* If there's an instance for which {@link start} has been called, returns it.
*
* @return the started instance
* @throws IllegalStateException if there isn't a started instance
*/
public static TrackDataHub getStartedInstance() {
if (startedInstance == null) {
throw new IllegalStateException("Data hub not started");
}
return startedInstance;
}
/**
* Stops listening to data sources and reporting the data to external
* listeners.
@@ -290,12 +273,12 @@ public class TrackDataHub {
Log.w(TAG, "Not started, ignoring");
return;
}
// Unregister internal listeners even if there are external listeners registered.
dataSourceManager.unregisterAllListeners();
listenerHandlerThread.getLooper().quit();
startedInstance = null;
started = false;
dataSources = null;
dataSourceManager = null;
@@ -304,7 +287,7 @@ public class TrackDataHub {
}
private boolean isStarted() {
return startedInstance != null;
return started;
}
@Override
@@ -412,10 +395,10 @@ public class TrackDataHub {
return;
}
// Save the selection to memory and flash.
// Save the selection to memory and flush.
selectedTrackId = trackId;
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
preferences.edit().putLong(SELECTED_TRACK_KEY, trackId));
selectedTrackId = trackId;
// Force it to reload data from the beginning.
Log.d(TAG, "Loading track");
@@ -642,6 +625,9 @@ public class TrackDataHub {
} else if (SPEED_REPORTING_KEY.equals(key)) {
reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
notifySpeedReportingChanged();
} else if (SELECTED_TRACK_KEY.equals(key)) {
long trackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
loadTrack(trackId);
}
}
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.io;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
@@ -25,20 +26,23 @@ import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.apps.mytracks.io.gdata.GDataWrapper.QueryFunction;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.googleapis.MethodOverrideIntercepter;
import com.google.api.client.googleapis.MethodOverride;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.javanet.NetHttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.Strings;
import android.app.Activity;
@@ -50,7 +54,6 @@ import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -64,6 +67,8 @@ import java.util.Vector;
*/
public class SendToFusionTables implements Runnable {
private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
/**
* Listener invoked when sending to fusion tables completes.
*/
@@ -76,13 +81,13 @@ public class SendToFusionTables implements Runnable {
/** The path for viewing a map visualization of a table. */
private static final String FUSIONTABLES_MAP =
"http://www.google.com/fusiontables/embedviz?" +
"https://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";
/** Standard base feed url for Fusion Tables. */
private static final String FUSIONTABLES_BASE_FEED_URL =
"http://www.google.com/fusiontables/api/query";
"https://www.google.com/fusiontables/api/query";
private static final int MAX_POINTS_PER_UPLOAD = 2048;
@@ -118,22 +123,15 @@ public class SendToFusionTables implements Runnable {
private int totalLocations;
private int totalSegmentsUploaded;
private HttpTransport transport;
private HttpRequestFactory httpRequestFactory;
private String tableId;
private static String MARKER_TYPE_START = "large_green";
private static String MARKER_TYPE_END = "large_red";
private static String MARKER_TYPE_WAYPOINT = "large_yellow";
static {
// We manually assign the transport to avoid having HttpTransport try to
// load it via reflection (which breaks due to ProGuard).
HttpTransport.setLowLevelHttpTransport(new NetHttpTransport());
}
public SendToFusionTables(Activity context, AuthManager auth,
long trackId, ProgressIndicator progressIndicator,
OnSendCompletedListener onCompletion) {
public SendToFusionTables(Activity context, AuthManager auth, long trackId,
ProgressIndicator progressIndicator, OnSendCompletedListener onCompletion) {
this.context = context;
this.auth = auth;
this.trackId = trackId;
@@ -142,13 +140,9 @@ public class SendToFusionTables implements Runnable {
this.stringUtils = new StringUtils(context);
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
headers.gdataVersion = GDATA_VERSION;
transport = new HttpTransport();
MethodOverrideIntercepter.setAsFirstFor(transport);
transport.defaultHeaders = headers;
HttpTransport transport = ApiFeatures.getInstance().useNetHttpTransport()
? new NetHttpTransport() : new ApacheHttpTransport();
httpRequestFactory = transport.createRequestFactory(new MethodOverride());
}
@Override
@@ -177,7 +171,6 @@ public class SendToFusionTables implements Runnable {
}
private void doUpload() {
((GoogleHeaders) transport.defaultHeaders).setGoogleLogin(auth.getAuthToken());
int statusMessageId = R.string.error_sending_to_fusiontables;
boolean success = true;
try {
@@ -280,7 +273,7 @@ public class SendToFusionTables implements Runnable {
*
* @param name the marker name
* @param description the marker description
* @param the marker location
* @param location the marker location
* @return true in case of success.
*/
private boolean createNewPoint(String name, String description, Location location,
@@ -306,7 +299,6 @@ public class SendToFusionTables implements Runnable {
}
private boolean uploadAllTrackPoints(final Track track, String originalDescription) {
SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
boolean metricUnits = true;
if (preferences != null) {
@@ -440,10 +432,10 @@ public class SendToFusionTables implements Runnable {
}
/**
* Returns a KML Point tag for the given location.
* Returns a KML LineString.
*
* @param location The location.
* @return the kml.
* @param track the track.
* @return the KML LineString.
*/
private String getKmlLineString(Track track) {
StringBuilder builder = new StringBuilder("<LineString><coordinates>");
@@ -500,7 +492,7 @@ public class SendToFusionTables implements Runnable {
* Prepares a buffer of locations for transmission to google fusion tables.
*
* @param track the original track with meta data
* @param buffer a buffer of locations on the track
* @param locations locations on the track
* @return an array of tracks each with a sub section of the points in the
* original buffer
*/
@@ -665,37 +657,44 @@ public class SendToFusionTables implements Runnable {
* @return true in case of success
*/
private boolean runUpdate(final String query) {
GDataWrapper<HttpTransport> wrapper = new GDataWrapper<HttpTransport>();
GDataWrapper<HttpRequestFactory> wrapper = new GDataWrapper<HttpRequestFactory>();
wrapper.setAuthManager(auth);
wrapper.setRetryOnAuthFailure(true);
wrapper.setClient(transport);
wrapper.setClient(httpRequestFactory);
Log.d(Constants.TAG, "GData connection prepared: " + this.auth);
wrapper.runQuery(new QueryFunction<HttpTransport>() {
wrapper.runQuery(new QueryFunction<HttpRequestFactory>() {
@Override
public void query(HttpTransport client)
public void query(HttpRequestFactory factory)
throws IOException, GDataWrapper.ParseException, GDataWrapper.HttpException,
GDataWrapper.AuthenticationException {
HttpRequest request = transport.buildPostRequest();
request.headers.contentType = "application/x-www-form-urlencoded";
GenericUrl url = new GenericUrl(FUSIONTABLES_BASE_FEED_URL);
request.url = url;
InputStreamContent isc = new InputStreamContent();
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
isc.inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
request.content = isc;
String sql = "sql=" + URLEncoder.encode(query, "UTF-8");
ByteArrayInputStream inputStream = new ByteArrayInputStream(Strings.toBytesUtf8(sql));
InputStreamContent isc = new InputStreamContent(null, inputStream );
HttpRequest request = factory.buildPostRequest(url, isc);
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("Google-MyTracks-" + SystemUtils.getMyTracksVersion(context));
headers.gdataVersion = GDATA_VERSION;
headers.setGoogleLogin(auth.getAuthToken());
headers.setContentType(CONTENT_TYPE);
request.setHeaders(headers);
Log.d(Constants.TAG, "Running update query " + url.toString() + ": " + sql);
HttpResponse response;
try {
response = request.execute();
} catch (HttpResponseException e) {
throw new GDataWrapper.HttpException(e.response.statusCode, e.response.statusMessage);
throw new GDataWrapper.HttpException(e.getResponse().getStatusCode(),
e.getResponse().getStatusMessage());
}
boolean success = response.isSuccessStatusCode;
boolean success = response.isSuccessStatusCode();
if (success) {
byte[] result = new byte[1024];
int read = response.getContent().read(result);
String s = new String(result, 0, read, Charset.forName("UTF8"));
String s = new String(result, 0, read, "UTF8");
String[] lines = s.split(Strings.LINE_SEPARATOR);
if (lines[0].equals("tableid")) {
tableId = lines[1];
@@ -704,9 +703,11 @@ public class SendToFusionTables implements Runnable {
Log.w(Constants.TAG, "Unrecognized response: " + lines[0]);
}
} else {
Log.d(Constants.TAG, "Query failed: " + response.statusMessage + " (" +
response.statusCode + ")");
throw new GDataWrapper.HttpException(response.statusCode, response.statusMessage);
Log.d(Constants.TAG,
"Query failed: " + response.getStatusMessage() + " ("
+ response.getStatusCode() + ")");
throw new GDataWrapper.HttpException(
response.getStatusCode(), response.getStatusMessage());
}
}
});
@@ -34,6 +34,7 @@ import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata2.client.AuthenticationException;
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
import java.io.BufferedReader;
@@ -44,6 +45,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -55,15 +57,15 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class DocsHelper {
private static final String DOCS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full";
"https://docs.google.com/feeds/documents/private/full";
private static final String DOCS_SPREADSHEET_URL =
"http://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
"https://docs.google.com/feeds/documents/private/full/spreadsheet%3A";
private static final String DOCS_WORKSHEETS_URL_FORMAT =
"http://spreadsheets.google.com/feeds/worksheets/%s/private/full";
"https://spreadsheets.google.com/feeds/worksheets/%s/private/full";
private static final String DOCS_MY_SPREADSHEETS_FEED_URL =
"http://docs.google.com/feeds/documents/private/full?category=mine,spreadsheet";
"https://docs.google.com/feeds/documents/private/full?category=mine,spreadsheet";
private static final String DOCS_SPREADSHEET_URL_FORMAT =
"http://spreadsheets.google.com/feeds/list/%s/%s/private/full";
"https://spreadsheets.google.com/feeds/list/%s/%s/private/full";
private static final String CONTENT_TYPE_PARAM = "Content-Type";
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
@@ -285,11 +287,16 @@ public class DocsHelper {
String elevationUnit = context.getString(metricUnits ?
R.string.meter : R.string.feet);
java.text.DateFormat dateFormat = DateFormat.getDateFormat(context);
java.text.DateFormat timeFormat = DateFormat.getTimeFormat(context);
Date startTime = new Date(stats.getStartTime());
String dateString = dateFormat.format(startTime) + " " + timeFormat.format(startTime);
// Prepare the Post-Text we are going to send.
DocsTagBuilder tagBuilder = new DocsTagBuilder(metricUnits)
.append("name", track.getName())
.append("description", track.getDescription())
.append("date", String.format("%tc", stats.getStartTime()))
.append("date", dateString)
.append("totaltime", StringUtils.formatTimeAlwaysShowingHours(
stats.getTotalTime()))
.append("movingtime", StringUtils.formatTimeAlwaysShowingHours(
@@ -15,40 +15,44 @@
*/
package com.google.android.apps.mytracks.io.docs;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
/**
* <p>This class builds a string of XML tags used to talk to Docs using GData.
*
*
* <p>Sample Usage:
*
*
* <code>
* String tags = new DocsTagBuilder(false)
* .append("tagName", "tagValue")
* .appendLargeUnits("bigTagName", 1.0)
* .build();
* </code>
*
*
* <p>results in:
*
*
* <code>
* <gsx:tagName><![CDATA[tagValue]]></gsx:tagName>
* <gsx:bigTagName><![CDATA[1.00]]></gsx:bigTagName>
* </code>
*
*
* @author Matthew Simmons
*/
class DocsTagBuilder {
// TODO(simmonmt): These formats aren't I18N-compatible. Not everyone uses
// commas for thousands and dots for the decimal point.
private static final DecimalFormatSymbols FORMAT_SYMBOLS =
ApiFeatures.getInstance().getApiPlatformAdapter().getDecimalFormatSymbols(Locale.ENGLISH);
private static final NumberFormat LARGE_UNIT_FORMAT =
new DecimalFormat("#,###,###.00");
new DecimalFormat("#,###,###.00", FORMAT_SYMBOLS);
private static final NumberFormat SMALL_UNIT_FORMAT =
new DecimalFormat("###,###");
new DecimalFormat("###,###", FORMAT_SYMBOLS);
protected final boolean metricUnits;
protected final StringBuilder stringBuilder;
@@ -59,7 +63,7 @@ class DocsTagBuilder {
*/
DocsTagBuilder(boolean metricUnits) {
this.metricUnits = metricUnits;
stringBuilder = new StringBuilder();
this.stringBuilder = new StringBuilder();
}
/** Appends a tag containing a string value */
@@ -68,13 +72,13 @@ class DocsTagBuilder {
return this;
}
/**
/**
* Appends a tag containing a numeric value. The value will be formatted
* according to the large distance of the specified measurement system (i.e.
* kilometers or miles).
*
*
* @param name The tag name.
* @param d The value to be formatted, in kilometers.
* @param d The value to be formatted, in kilometers.
*/
DocsTagBuilder appendLargeUnits(String name, double d) {
double value = metricUnits ? d : (d * UnitConversions.KM_TO_MI);
@@ -86,7 +90,7 @@ class DocsTagBuilder {
* Appends a tag containing a numeric value. The value will be formatted
* according to the small distance of the specified measurement system (i.e.
* meters or feet).
*
*
* @param name The tag name.
* @param d The value to be formatted, in meters.
*/
@@ -44,6 +44,7 @@ import java.util.TimeZone;
* Garmin Training Center 3.5.3.
*
* @author Sandor Dornbush
* @author Dominik Ršttsches
*/
public class TcxTrackWriter implements TrackFormatWriter {
protected static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
@@ -64,6 +65,9 @@ public class TcxTrackWriter implements TrackFormatWriter {
private PrintWriter pw = null;
private Track track;
// Determines whether to encode cadence value as running or cycling cadence.
private boolean sportIsCycling;
public TcxTrackWriter(Context context) {
this.context = context;
@@ -76,7 +80,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
public void prepare(Track track, OutputStream out) {
this.track = track;
this.pw = new PrintWriter(out);
this.sportIsCycling = categoryToTcxSport(track.getCategory()).equals(TCX_SPORT_BIKING);
}
@Override
@@ -174,23 +178,46 @@ public class TcxTrackWriter implements TrackFormatWriter {
pw.print("</Value>");
pw.println("</HeartRateBpm>");
}
if (sensorData.hasPower()
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
&& sensorData.getPower().hasValue()) {
pw.print(" <Extensions>");
pw.print("<TPX xmlns=\"http://www.garmin.com/xmlschemas/ActivityExtension/v2\">");
pw.print("<Watts>");
pw.print(sensorData.getPower().getValue());
pw.print("</Watts>");
pw.println("</TPX></Extensions>");
}
if (sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
boolean cadenceAvailable = sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue();
// TCX Trackpoint_t contains a sequence. Thus, the legacy XML element
// <Cadence> needs to be put before <Extensions>.
// This field should only be used for the case that activity was marked as biking.
// Otherwise cadence is interpreted as running cadence data which
// is written in the <Extensions> as <RunCadence>.
if (sportIsCycling && cadenceAvailable) {
pw.print(" <Cadence>");
pw.print(Math.min(254, sensorData.getCadence().getValue()));
pw.println("</Cadence>");
}
boolean powerAvailable = sensorData.hasPower()
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
&& sensorData.getPower().hasValue();
if(powerAvailable || (!sportIsCycling && cadenceAvailable)) {
pw.print(" <Extensions>");
pw.print("<TPX xmlns=\"http://www.garmin.com/xmlschemas/ActivityExtension/v2\">");
// RunCadence needs to be put before power in order to be understood
// by Garmin Training Center.
if (!sportIsCycling && cadenceAvailable) {
pw.print("<RunCadence>");
pw.print(Math.min(254, sensorData.getCadence().getValue()));
pw.print("</RunCadence>");
}
if (powerAvailable) {
pw.print("<Watts>");
pw.print(sensorData.getPower().getValue());
pw.print("</Watts>");
}
pw.println("</TPX></Extensions>");
}
}
}
pw.println(" </Trackpoint>");
@@ -216,11 +243,9 @@ public class TcxTrackWriter implements TrackFormatWriter {
// TCX schema.
pw.println("<UnitId>0</UnitId>");
pw.println("<ProductID>0</ProductID>");
writeVersion();
pw.println("<Version>");
pw.println("<VersionMajor>0</VersionMajor>");
pw.println("<VersionMinor>0</VersionMinor>");
pw.println("</Version>");
pw.println("</Creator>");
pw.println(" </Activity>");
pw.println(" </Activities>");
@@ -241,15 +266,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
pw.println("<Build>");
// The TCX spec for the Version tag is too strict to allow us to use our
// user-visible version number, so we use our version code instead. We
// also set the minor version to 1 if this is a development build to
// signify that this build is newer than the one associated with the
// version code given in VersionMajor.
pw.println("<Version>");
pw.format("<VersionMajor>%s</VersionMajor>\n", SystemUtils.getMyTracksVersion(context));
pw.format("<VersionMinor>%d</VersionMinor>\n", SystemUtils.isRelease(context) ? 0 : 1);
pw.println("</Version>");
writeVersion();
pw.format("<Type>%s</Type>\n", SystemUtils.isRelease(context) ? TCX_TYPE_RELEASE
: TCX_TYPE_INTERNAL);
@@ -264,6 +281,30 @@ public class TcxTrackWriter implements TrackFormatWriter {
public void writeWaypoint(Waypoint waypoint) {
// TODO Write out the waypoints somewhere.
}
private void writeVersion() {
if (pw == null) {
return;
}
// Splitting the myTracks version code into VersionMajor, VersionMinor and BuildMajor
// to fit the integer type requirement for these fields in the TCX spec.
// Putting a string like "x.x.x" into VersionMajor breaks XML validation.
// We also set the BuildMinor version to 1 if this is a development build to
// signify that this build is newer than the one associated with the
// version code given in BuildMajor.
String[] myTracksVersionComponents = SystemUtils.getMyTracksVersion(context).split("\\.");
pw.println("<Version>");
pw.format("<VersionMajor>%d</VersionMajor>\n", Integer.valueOf(myTracksVersionComponents[0]));
pw.format("<VersionMinor>%d</VersionMinor>\n", Integer.valueOf(myTracksVersionComponents[1]));
// TCX schema says these are optional but http://connect.garmin.com only accepts
// the TCX file when they are present.
pw.format("<BuildMajor>%d</BuildMajor>\n", Integer.valueOf(myTracksVersionComponents[2]));
pw.format("<BuildMinor>%d</BuildMinor>\n", SystemUtils.isRelease(context) ? 0 : 1);
pw.println("</Version>");
}
private String categoryToTcxSport(String category) {
category = category.trim();
@@ -70,6 +70,11 @@ class WriteProgressController {
public void setOnCompletionListener(OnCompletionListener onCompletionListener) {
this.onCompletionListener = onCompletionListener;
}
// For testing purpose
OnCompletionListener getOnCompletionListener() {
return onCompletionListener;
}
public ProgressDialog createProgressDialog() {
dialog = new ProgressDialog(activity);
@@ -7,7 +7,7 @@ package com.google.android.apps.mytracks.io.mymaps;
public class MyMapsConstants {
static final String TAG = "MapsApi";
static final String MAPSHOP_BASE_URL =
"http://maps.google.com/maps/ms";
"https://maps.google.com/maps/ms";
public static final String SERVICE_NAME = "local";
/**
@@ -0,0 +1,129 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.util.Log;
/**
* A dynamic speed path descriptor.
*
* @author Vangelis S.
*/
public class DynamicSpeedTrackPathDescriptor
implements TrackPathDescriptor, OnSharedPreferenceChangeListener {
private int slowSpeed;
private int normalSpeed;
private int speedMargin;
private double averageMovingSpeed;
private final Context context;
public DynamicSpeedTrackPathDescriptor(Context context){
this.context = context;
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
speedMargin = 25;
return;
}
prefs.registerOnSharedPreferenceChangeListener(this);
speedMargin = Integer.parseInt(prefs.getString(
context.getString(R.string.track_color_mode_dynamic_speed_variation_key), "25"));
}
/**
* Get the slow speed calculated based on the % below the average speed.
* @return The speed limit considered as slow.
*/
public int getSlowSpeed()
{
slowSpeed = (int) (averageMovingSpeed - (averageMovingSpeed * speedMargin / 100));
return slowSpeed;
}
/**
* Get the medium speed calculated based on the % above the average speed.
* @return The speed limit considered as normal.
*/
public int getNormalSpeed()
{
normalSpeed = (int) (averageMovingSpeed + (averageMovingSpeed * speedMargin / 100));
return normalSpeed;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d(TAG, "DynamicSpeedTrackPathDescriptor: onSharedPreferences changed " + key);
if (key == null
|| !key.equals(context.getString(R.string.track_color_mode_dynamic_speed_variation_key))) {
return;
}
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
speedMargin = 25;
return;
}
speedMargin =
Integer.parseInt(
prefs.getString(
context.getString(R.string.track_color_mode_dynamic_speed_variation_key), "25"));
}
@Override
public boolean needsRedraw() {
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
long currentTrackId = prefs.getLong(context.getString(R.string.selected_track_key), -1);
if(currentTrackId == -1) {
// Could not find track.
return false;
}
Track track = MyTracksProviderUtils.Factory.get(context).getTrack(currentTrackId);
TripStatistics stats = track.getStatistics();
double newaverageSpeed = (int) Math.floor(stats.getAverageMovingSpeed() * 3.6);
if(averageMovingSpeed == 0) {
averageMovingSpeed = newaverageSpeed;
return true;
}
double difference = Math.max(averageMovingSpeed, newaverageSpeed);
if (difference == 0.0) {
difference = 0.0;
} else {
difference = Math.abs(averageMovingSpeed - newaverageSpeed) / difference * 100;
}
if(difference >= 20) {
averageMovingSpeed = newaverageSpeed;
return true;
}
return false;
}
}
@@ -0,0 +1,141 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.ColoredPath;
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.Projection;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import java.util.ArrayList;
import java.util.List;
/**
* A path painter that varies the path colors based on fixed speeds or average speed margin
* depending of the TrackPathDescriptor passed to its constructor.
*
* @author Vangelis S.
*/
public class DynamicSpeedTrackPathPainter implements TrackPathPainter {
private final Paint selectedTrackPaintSlow;
private final Paint selectedTrackPaintMedium;
private final Paint selectedTrackPaintFast;
private final List<ColoredPath> coloredPaths;
private final TrackPathDescriptor trackPathDescriptor;
private int slowSpeed;
private int normalSpeed;
public DynamicSpeedTrackPathPainter (Context context, TrackPathDescriptor trackPathDescriptor) {
this.trackPathDescriptor = trackPathDescriptor;
selectedTrackPaintSlow = TrackPathUtilities.getPaint(R.color.slow_path, context);
selectedTrackPaintMedium = TrackPathUtilities.getPaint(R.color.normal_path, context);
selectedTrackPaintFast = TrackPathUtilities.getPaint(R.color.fast_path, context);
this.coloredPaths = new ArrayList<ColoredPath>();
}
@Override
public void drawTrack(Canvas canvas) {
for(int i = 0; i < coloredPaths.size(); ++i) {
ColoredPath coloredPath = coloredPaths.get(i);
canvas.drawPath(coloredPath.getPath(), coloredPath.getPathPaint());
}
}
@Override
public void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
Boolean alwaysVisible, List<CachedLocation> points) {
// Whether to start a new segment on new valid and visible point.
boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid;
boolean lastVisible = !newSegment;
final Point pt = new Point();
clear();
slowSpeed = trackPathDescriptor.getSlowSpeed();
normalSpeed = trackPathDescriptor.getNormalSpeed();
// Loop over track points.
for (int i = startLocationIdx; i < points.size(); ++i) {
CachedLocation loc = points.get(i);
// Check if valid, if not then indicate a new segment.
if (!loc.valid) {
newSegment = true;
continue;
}
final GeoPoint geoPoint = loc.geoPoint;
// Check if this breaks the existing segment.
boolean visible = alwaysVisible || viewRect.contains(
geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
if (!visible && !lastVisible) {
// This is a point outside view not connected to a visible one.
newSegment = true;
}
lastVisible = visible;
// Either move to beginning of a new segment or continue the old one.
if (newSegment) {
projection.toPixels(geoPoint, pt);
newSegment = false;
} else {
ColoredPath coloredPath;
if(loc.speed <= slowSpeed) {
coloredPath = new ColoredPath(selectedTrackPaintSlow);
}
else if(loc.speed <= normalSpeed) {
coloredPath = new ColoredPath(selectedTrackPaintMedium);
} else {
coloredPath = new ColoredPath(selectedTrackPaintFast);
}
coloredPath.getPath().moveTo(pt.x, pt.y);
projection.toPixels(geoPoint, pt);
coloredPath.getPath().lineTo(pt.x, pt.y);
coloredPaths.add(coloredPath);
}
}
}
@Override
public void clear()
{
coloredPaths.clear();
}
@Override
public boolean needsRedraw() {
return trackPathDescriptor.needsRedraw();
}
@Override
public Path getLastPath() {
Path path = new Path();
for(int i = 0; i < coloredPaths.size(); ++i) {
path.addPath(coloredPaths.get(i).getPath());
}
return path;
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.util.Log;
/**
* A fixed speed path descriptor.
*
* @author Vangelis S.
*/
public class FixedSpeedTrackPathDescriptor implements TrackPathDescriptor, OnSharedPreferenceChangeListener {
private int slowSpeed;
private int normalSpeed;
private final Context context;
public FixedSpeedTrackPathDescriptor(Context context) {
this.context = context;
context.getSharedPreferences(Constants.SETTINGS_NAME, 0)
.registerOnSharedPreferenceChangeListener(this);
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
slowSpeed = 9;
normalSpeed = 17;
return;
}
try {
slowSpeed = Integer.parseInt(prefs.getString(context.getString(
R.string.track_color_mode_fixed_speed_slow_key), "9"));
} catch (NumberFormatException e) {
slowSpeed = 9;
}
try {
normalSpeed = Integer.parseInt(prefs.getString(context.getString(
R.string.track_color_mode_fixed_speed_medium_key), "17"));
} catch (NumberFormatException e) {
normalSpeed = 17;
}
}
/**
* Gets the slow speed for reference.
* @return The speed limit considered as slow.
*/
public int getSlowSpeed() {
return slowSpeed;
}
/**
* Gets the normal speed for reference.
* @return The speed limit considered as normal.
*/
public int getNormalSpeed() {
return normalSpeed;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d(TAG, "FixedSpeedTrackPathDescriptor: onSharedPreferences changed " + key);
if (key == null
|| (!key.equals(context.getString(R.string.track_color_mode_fixed_speed_slow_key))
&& !key.equals(context.getString(R.string.track_color_mode_fixed_speed_medium_key)))) {
return;
}
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
slowSpeed = 9;
normalSpeed = 17;
return;
}
try {
slowSpeed = Integer.parseInt(prefs.getString(context.getString(
R.string.track_color_mode_fixed_speed_slow_key), "9"));
} catch (NumberFormatException e) {
slowSpeed = 9;
}
try {
normalSpeed = Integer.parseInt(prefs.getString(context.getString(
R.string.track_color_mode_fixed_speed_medium_key), "17"));
} catch (NumberFormatException e) {
normalSpeed = 17;
}
}
@Override
public boolean needsRedraw() {
return false;
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.Projection;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import java.util.List;
/**
* A path painter that not variates the path colors.
*
* @author Vangelis S.
*/
public class SingleColorTrackPathPainter implements TrackPathPainter {
private final Paint selectedTrackPaint;
private Path path;
public SingleColorTrackPathPainter(Context context) {
selectedTrackPaint = TrackPathUtilities.getPaint(R.color.red, context);
}
@Override
public void drawTrack(Canvas canvas) {
canvas.drawPath(path, selectedTrackPaint);
}
@Override
public void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
Boolean alwaysVisible, List<CachedLocation> points) {
// Whether to start a new segment on new valid and visible point.
boolean newSegment = startLocationIdx <= 0 || !points.get(startLocationIdx - 1).valid;
boolean lastVisible = !newSegment;
final Point pt = new Point();
// Loop over track points.
int numPoints = points.size();
path = newPath();
path.incReserve(numPoints);
for (int i = startLocationIdx; i < numPoints ; ++i) {
CachedLocation loc = points.get(i);
// Check if valid, if not then indicate a new segment.
if (!loc.valid) {
newSegment = true;
continue;
}
final GeoPoint geoPoint = loc.geoPoint;
// Check if this breaks the existing segment.
boolean visible = alwaysVisible
|| viewRect.contains(geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
if (!visible && !lastVisible) {
// This is a point outside view not connected to a visible one.
newSegment = true;
}
lastVisible = visible;
// Either move to beginning of a new segment or continue the old one.
projection.toPixels(geoPoint, pt);
if (newSegment) {
path.moveTo(pt.x, pt.y);
newSegment = false;
} else {
path.lineTo(pt.x, pt.y);
}
}
}
@Override
public void clear() {
path = null;
}
@Override
public boolean needsRedraw() {
return false;
}
@Override
public Path getLastPath() {
return path;
}
// Visible for testing
public Path newPath() {
return new Path();
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
/**
* An interface for classes which describe how to draw a track path.
*
* @author Vangelis S.
*/
public interface TrackPathDescriptor {
/**
* @return The maximum speed which is considered slow.
*/
int getSlowSpeed();
/**
* @return The maximum speed which is considered normal.
*/
int getNormalSpeed();
/**
* @return True if the path needs to be updated.
*/
boolean needsRedraw();
}
@@ -0,0 +1,68 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.MapOverlay.CachedLocation;
import com.google.android.maps.Projection;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
import java.util.List;
/**
* An interface for classes which paint the track path.
*
* @author Vangelis S.
*/
public interface TrackPathPainter {
/**
* Clears the related data.
*/
void clear();
/**
* Draws the path to the canvas.
* @param canvas The Canvas to draw upon
*/
void drawTrack(Canvas canvas);
/**
* Updates the path.
* @param projection The Canvas to draw upon.
* @param viewRect The Path to be drawn.
* @param startLocationIdx The start point from where update the path.
* @param alwaysVisible Flag for alwaysvisible.
* @param points The list of points used to update the path.
*/
void updatePath(Projection projection, Rect viewRect, int startLocationIdx,
Boolean alwaysVisible, List<CachedLocation> points);
/**
* @return True if the path needs to be updated.
*/
boolean needsRedraw();
/**
* @return The path being used currently.
*/
Path getLastPath();
}
@@ -0,0 +1,65 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
/**
* A factory for TrackPathPainters.
*
* @author Vangelis S.
*/
public class TrackPathPainterFactory {
private TrackPathPainterFactory() {
}
/**
* Get a new TrackPathPainter.
* @param context Context to fetch system preferences.
* @return The TrackPathPainter that corresponds to the track color mode setting.
*/
public static TrackPathPainter getTrackPathPainter(Context context) {
SharedPreferences prefs =
context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
return new SingleColorTrackPathPainter(context);
}
String colorMode = prefs.getString(context.getString(R.string.track_color_mode_key), null);
Log.i(TAG, "Creating track path painter of type: " + colorMode);
if (colorMode == null || colorMode.equals(context.getString(R.string.track_color_mode_none))) {
return new SingleColorTrackPathPainter(context);
} else if (colorMode.equals(context.getString(R.string.track_color_mode_fixed))) {
return new DynamicSpeedTrackPathPainter(context,
new FixedSpeedTrackPathDescriptor(context));
} else if (colorMode.equals(context.getString(R.string.track_color_mode_dynamic))) {
return new DynamicSpeedTrackPathPainter(context,
new DynamicSpeedTrackPathDescriptor(context));
} else {
Log.w(TAG, "Using default track path painter. Unrecognized painter: " + colorMode);
return new SingleColorTrackPathPainter(context);
}
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import android.content.Context;
import android.graphics.Paint;
/**
* Various utility functions for TrackPath painting.
*
* @author Vangelis S.
*/
public class TrackPathUtilities {
public static Paint getPaint(int id, Context context) {
Paint paint = new Paint();
paint.setColor(context.getResources().getColor(id));
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
return paint;
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.widgets.TrackWidgetProvider;
import com.google.android.maps.mytracks.R;
import android.app.IntentService;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
/**
* A service to control starting and stopping of a recording. This service,
* through the AndroidManifest.xml, is configured to only allow components of
* the same application to invoke it. Thus this service can be used my MyTracks
* app widget, {@link TrackWidgetProvider}, but not by other applications. This
* application delegates starting and stopping a recording to
* {@link TrackRecordingService} using RPC calls.
*
* @author jshih@google.com (Jimmy Shih)
*/
public class ControlRecordingService extends IntentService implements ServiceConnection {
private ITrackRecordingService trackRecordingService;
private boolean connected = false;
public ControlRecordingService() {
super(ControlRecordingService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
Intent newIntent = new Intent(this, TrackRecordingService.class);
startService(newIntent);
bindService(newIntent, this, 0);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
trackRecordingService = ITrackRecordingService.Stub.asInterface(service);
notifyConnected();
}
@Override
public void onServiceDisconnected(ComponentName name) {
connected = false;
}
/**
* Notifies all threads that connection to {@link TrackRecordingService} is
* available.
*/
private synchronized void notifyConnected() {
connected = true;
notifyAll();
}
/**
* Waits until the connection to {@link TrackRecordingService} is available.
*/
private synchronized void waitConnected() {
while (!connected) {
try {
wait();
} catch (InterruptedException e) {
// can safely ignore
}
}
}
@Override
protected void onHandleIntent(Intent intent) {
waitConnected();
String action = intent.getAction();
if (action != null) {
try {
if (action.equals(getString(R.string.start_new_track_action))) {
trackRecordingService.startNewTrack();
} else if (action.equals(getString(R.string.end_current_track_action))) {
trackRecordingService.endCurrentTrack();
}
} catch (RemoteException e) {
Log.d(TAG, "ControlRecordingService onHandleIntent RemoteException", e);
}
}
unbindService(this);
connected = false;
}
@Override
public void onDestroy() {
super.onDestroy();
if (connected) {
unbindService(this);
connected = false;
}
}
}
@@ -56,11 +56,13 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.util.Log;
import java.util.Timer;
@@ -153,6 +155,9 @@ public class TrackRecordingService extends Service {
@Override
public void onLocationChanged(final Location location) {
if (executorService.isShutdown() || executorService.isTerminated()) {
return;
}
executorService.submit(
new Runnable() {
@Override
@@ -226,6 +231,13 @@ public class TrackRecordingService extends Service {
* Application lifetime events:
*/
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the onCreate
* callback, we cannot tell whether the caller is MyTracks or a third party
* app, thus it cannot start/stop a recording or write/update MyTracks
* database. However, it can resume a recording.
*/
@Override
public void onCreate() {
super.onCreate();
@@ -265,40 +277,48 @@ public class TrackRecordingService extends Service {
showNotification();
}
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the onStart
* callback, we cannot tell whether the caller is MyTracks or a third party
* app, thus it cannot start/stop a recording or write/update MyTracks
* database. However, it can resume a recording.
*/
@Override
public void onStart(Intent intent, int startId) {
handleStartCommand(intent, startId);
}
/*
* Note that this service, through the AndroidManifest.xml, is configured to
* allow both MyTracks and third party apps to invoke it. For the
* onStartCommand callback, we cannot tell whether the caller is MyTracks or a
* third party app, thus it cannot start/stop a recording or write/update
* MyTracks database. However, it can resume a recording.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleStartCommand(intent, startId);
return START_STICKY;
}
private void handleStartCommand(Intent intent, int startId) {
Log.d(TAG,
"TrackRecordingService.handleStartCommand: " + startId);
Log.d(TAG, "TrackRecordingService.handleStartCommand: " + startId);
if (intent == null) {
return;
}
// Check if called on phone reboot with resume intent.
if (intent != null) {
if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
resumeTrack(startId);
} else {
// Process actions for controlling the service.
String action = intent.getAction();
if (getString(R.string.start_new_track_action).equals(action)) {
startNewTrack();
if (intent.getBooleanExtra(getString(R.string.select_new_track_extra), false)) {
prefManager.setSelectedTrack(recordingTrackId);
}
} else if (getString(R.string.end_current_track_action).equals(action)) {
endCurrentTrack();
}
}
if (intent.getBooleanExtra(RESUME_TRACK_EXTRA_NAME, false)) {
resumeTrack(startId);
}
}
private boolean isTrackInProgress() {
return recordingTrackId != -1 || isRecording;
}
private void resumeTrack(int startId) {
Log.d(TAG, "TrackRecordingService: requested resume");
@@ -342,7 +362,6 @@ public class TrackRecordingService extends Service {
timer.purge();
unregisterLocationListener();
shutdownTaskExecutors();
executorService.shutdown();
if (sensorManager != null) {
sensorManager.shutdown();
sensorManager = null;
@@ -358,6 +377,8 @@ public class TrackRecordingService extends Service {
// This should be the last operation.
releaseWakeLock();
// Shutdown the executor service last to avoid sending events to a dead executor.
executorService.shutdown();
super.onDestroy();
}
@@ -543,10 +564,10 @@ public class TrackRecordingService extends Service {
* Recording lifecycle.
*/
public long startNewTrack() {
private long startNewTrack() {
Log.d(TAG, "TrackRecordingService.startNewTrack");
if (recordingTrackId != -1 || isRecording) {
throw new IllegalStateException("A track is already in progress!");
if (isTrackInProgress()) {
return -1L;
}
long startTime = System.currentTimeMillis();
@@ -954,8 +975,7 @@ public class TrackRecordingService extends Service {
* Build a statistics marker.
* A statistics marker holds the stats for the* last segment up to this marker.
*
* @param Waypoint The waypoint which will be populated with stats data.
* @return the unique id of the inserted marker
* @param waypoint The waypoint which will be populated with stats data.
*/
private void buildStatisticsMarker(Waypoint waypoint) {
StringUtils utils = new StringUtils(TrackRecordingService.this);
@@ -983,8 +1003,8 @@ public class TrackRecordingService extends Service {
private void endCurrentTrack() {
Log.d(TAG, "TrackRecordingService.endCurrentTrack");
if (recordingTrackId == -1 || !isRecording) {
throw new IllegalStateException("No recording track in progress!");
if (!isTrackInProgress()) {
return;
}
announcementExecutor.shutdown();
@@ -1025,12 +1045,15 @@ public class TrackRecordingService extends Service {
}
private void sendTrackBroadcast(int actionResId, long trackId) {
Intent broadcastIntent =
new Intent()
.setAction(getString(actionResId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent,
getString(R.string.broadcast_notifications_permission));
Intent broadcastIntent = new Intent()
.setAction(getString(actionResId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent, getString(R.string.mytracks_notifications_permission));
SharedPreferences sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (sharedPreferences.getBoolean(getString(R.string.allow_access_key), false)) {
sendBroadcast(broadcastIntent, getString(R.string.broadcast_notifications_permission));
}
}
/*
@@ -1162,29 +1185,58 @@ public class TrackRecordingService extends Service {
}
}
/**
* Checks if the service is available. If not, throws an
* {@link IllegalStateException}.
*/
private void checkService() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
}
/**
* Returns true if the RPC caller is from the same application or if the
* "Allow access" setting indicates that another app can invoke this service's
* RPCs.
*/
private boolean canAccess() {
// As a precondition for access, must check if the service is available.
checkService();
if (Process.myPid() == Binder.getCallingPid()) {
return true;
} else {
SharedPreferences sharedPreferences = service.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
return sharedPreferences.getBoolean(service.getString(R.string.allow_access_key), false);
}
}
// Service method delegates.
@Override
public boolean isRecording() {
checkService();
if (!canAccess()) {
return false;
}
return service.isRecording();
}
@Override
public long getRecordingTrackId() {
checkService();
if (!canAccess()) {
return -1L;
}
return service.recordingTrackId;
}
@Override
public long startNewTrack() {
checkService();
if (!canAccess()) {
return -1L;
}
return service.startNewTrack();
}
@@ -1195,25 +1247,33 @@ public class TrackRecordingService extends Service {
* @return the unique ID of the inserted marker
*/
public long insertWaypoint(WaypointCreationRequest request) {
checkService();
if (!canAccess()) {
return -1L;
}
return service.insertWaypoint(request);
}
@Override
public void endCurrentTrack() {
checkService();
if (!canAccess()) {
return;
}
service.endCurrentTrack();
}
@Override
public void recordLocation(Location loc) {
checkService();
if (!canAccess()) {
return;
}
service.locationListener.onLocationChanged(loc);
}
@Override
public byte[] getSensorData() {
checkService();
if (!canAccess()) {
return null;
}
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return null;
@@ -1227,7 +1287,9 @@ public class TrackRecordingService extends Service {
@Override
public int getSensorState() {
checkService();
if (!canAccess()) {
return Sensor.SensorState.NONE.getNumber();
}
if (service.sensorManager == null) {
Log.d(TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber();
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -261,22 +262,29 @@ public class BluetoothConnectionManager {
}
private BluetoothSocket getSocket() throws IOException {
try {
Class<? extends BluetoothDevice> c = device.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket",
Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(device, 1);
} catch (SecurityException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (NoSuchMethodException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalAccessException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (InvocationTargetException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
if (ApiFeatures.getInstance().hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord()) {
try {
return device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
}
} else {
try {
Class<? extends BluetoothDevice> c = device.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket", Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(device, 1);
} catch (SecurityException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (NoSuchMethodException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalAccessException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
} catch (InvocationTargetException e) {
Log.e(Constants.TAG, "Unable to get insecure connect.", e);
}
}
return device.createRfcommSocketToServiceRecord(SPP_UUID);
}
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.services.sensors;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
@@ -24,10 +26,13 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
/**
* Manage the connection to a bluetooth sensor.
*
@@ -36,8 +41,8 @@ import android.widget.Toast;
public class BluetoothSensorManager extends SensorManager {
// Local Bluetooth adapter
private BluetoothAdapter bluetoothAdapter = null;
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
// Member object for the sensor threads and connections.
private BluetoothConnectionManager connectionManager = null;
@@ -54,8 +59,6 @@ public class BluetoothSensorManager extends SensorManager {
Context context, MessageParser parser) {
this.context = context;
this.parser = parser;
// Get local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// If BT is not available or not enabled quit.
if (!isEnabled()) {
return;
@@ -71,6 +74,50 @@ public class BluetoothSensorManager extends SensorManager {
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
}
/**
* Code for assigning the local bluetooth adapter
*
* @return The default bluetooth adapter, if one is available, NULL if it isn't.
*/
private static BluetoothAdapter getDefaultBluetoothAdapter() {
// Check if the calling thread is the main application thread,
// if it is, do it directly.
if (Thread.currentThread().equals(Looper.getMainLooper().getThread())) {
return BluetoothAdapter.getDefaultAdapter();
}
// If the calling thread, isn't the main application thread,
// then get the main application thread to return the default adapter.
final ArrayList<BluetoothAdapter> adapters = new ArrayList<BluetoothAdapter>(1);
final Object mutex = new Object();
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
adapters.add(BluetoothAdapter.getDefaultAdapter());
synchronized (mutex) {
mutex.notify();
}
}
});
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(1000L);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
}
}
if (adapters.get(0) == null) {
Log.w(TAG, "No bluetooth adapter found!");
}
return adapters.get(0);
}
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
@@ -125,7 +172,7 @@ public class BluetoothSensorManager extends SensorManager {
}
// The Handler that gets information back from the BluetoothSensorService
private final Handler messageHandler = new Handler() {
private final Handler messageHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -0,0 +1,80 @@
/*
* 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 java.util.LinkedList;
import java.util.List;
/**
* A history of Zephyr stride counter reading.
* These can be used as an alternate method to calculate the correct cadence.
* This is a work around for an issue with some HxM firmware.
*
* @author Dominik Ršttsches
*/
public class StrideReadings {
// visible for testing
protected static final int NUM_READINGS_FOR_AVERAGE = 10;
protected static final int MIN_READINGS_FOR_AVERAGE = 5;
protected static final int CADENCE_NOT_AVAILABLE = -1;
// TODO: Check whether 1Hz assumption is okay for cadence calculation
// otherwise add heart beat timestamp to this list and compute
// cadence from these timestamps.
private final List<Integer> strideReadingsHistory;
public StrideReadings() {
strideReadingsHistory = new LinkedList<Integer>();
}
public void updateStrideReading(int numStrides) {
// HRM/HxM documentation says, transmission frequency is 1 Hz,
// let's keep last NUM_READINGS_FOR_AVERAGE readings.
// TODO: Calibrate this using a reliable footpod / cadence sensor,
// otherwise use heartbeat timestamp for calculation.
strideReadingsHistory.add(0, numStrides);
while (strideReadingsHistory.size() > NUM_READINGS_FOR_AVERAGE) {
strideReadingsHistory.remove(strideReadingsHistory.size()-1);
}
}
public int getCadence() {
if (strideReadingsHistory.size() < MIN_READINGS_FOR_AVERAGE) {
// Bail out if we cannot really get a meaningful average yet.
return CADENCE_NOT_AVAILABLE;
}
// Compute assuming 1 stride reading/second.
int timeSinceOldestReadingSecs = strideReadingsHistory.size() - 1;
int stridesThen = strideReadingsHistory.get(strideReadingsHistory.size()-1);
int stridesNow = strideReadingsHistory.get(0);
// Contrary to documentation stride value seems to roll over at 128.
return Math.round( (float)(mod((stridesNow - stridesThen), 128)) /
timeSinceOldestReadingSecs * 60);
}
/**
* Modulo operation with positive return values, Java's remainder operator doesn't change sign.
*
* @return x mod y
*/
private static int mod(int x, int y)
{
int result = x % y;
return result < 0 ? result + y : result;
}
}
@@ -15,50 +15,72 @@
*/
package com.google.android.apps.mytracks.services.sensors;
import android.util.Log;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.util.ApiFeatures;
import java.util.Arrays;
/**
* An implementation of a Sensor MessageParser for Zephyr.
*
* @author Sandor Dornbush
* @author Dominik Ršttsches
*/
public class ZephyrMessageParser implements MessageParser {
public static final int ZEPHYR_HXM_BYTE_STX = 0;
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
private static final byte[] CADENCE_BUG_FW_ID = {0x1A, 0x00, 0x31, 0x65, 0x50, 0x00, 0x31, 0x62};
private StrideReadings strideReadings;
@Override
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffer.length; i++) {
sb.append(String.format("%02X", buffer[i]));
}
Log.w(Constants.TAG, "Got zephyr data: " + sb);
// Heart Rate
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
.setValue(buffer[12] & 0xFF)
.setState(Sensor.SensorState.SENDING);
// Changes Nico Laum (Power and Cadence)
Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder()
.setValue(buffer[11])
.setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder()
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorDataSet sds =
Sensor.SensorDataSet.Builder sds =
Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis())
.setPower(power)
.setHeartRate(heartrate)
.setCadence(cadence)
.build();
.setCreationTime(System.currentTimeMillis());
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
.setValue(buffer[12] & 0xFF)
.setState(Sensor.SensorState.SENDING);
sds.setHeartRate(heartrate);
return sds;
Sensor.SensorData.Builder batteryLevel = Sensor.SensorData.newBuilder()
.setValue(buffer[11])
.setState(Sensor.SensorState.SENDING);
sds.setBatteryLevel(batteryLevel);
setCadence(sds, buffer);
return sds.build();
}
private void setCadence(Sensor.SensorDataSet.Builder sds, byte[] buffer) {
// Device Firmware ID, Firmware Version, Hardware ID, Hardware Version
// 0x1A00316550003162 produces erroneous values for Cadence and needs
// a workaround based on the stride counter.
// Firmware values range from field 3 to 10 (inclusive) of the byte buffer.
byte[] hardwareFirmwareId = ApiFeatures.getInstance().getApiPlatformAdapter()
.copyByteArray(buffer, 3, 11);
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder();
if (Arrays.equals(hardwareFirmwareId, CADENCE_BUG_FW_ID)) {
if (strideReadings == null) {
strideReadings = new StrideReadings();
}
strideReadings.updateStrideReading(buffer[54] & 0xFF);
if (strideReadings.getCadence() != StrideReadings.CADENCE_NOT_AVAILABLE) {
cadence.setValue(strideReadings.getCadence()).setState(Sensor.SensorState.SENDING);
}
} else {
cadence
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
.setState(Sensor.SensorState.SENDING);
}
sds.setCadence(cadence);
}
@Override
@@ -16,7 +16,11 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.Constants;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import android.bluetooth.BluetoothDevice;
import android.os.Build;
import android.util.Log;
@@ -31,7 +35,7 @@ public class ApiFeatures {
/**
* The API level of the Android version we're being run under.
*/
public static final int ANDROID_API_LEVEL = Integer.parseInt(
private static final int ANDROID_API_LEVEL = Integer.parseInt(
Build.VERSION.SDK);
private static ApiFeatures instance;
@@ -80,38 +84,17 @@ public class ApiFeatures {
return apiPlatformAdapter;
}
/**
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
*/
public boolean hasBackup() {
return getApiLevel() >= 8;
}
// API Level 4 Changes
/**
* Returns whether text-to-speech is available.
*/
public boolean hasTextToSpeech() {
if (getApiLevel() < 4) return false;
try {
Class.forName("android.speech.tts.TextToSpeech");
} catch (ClassNotFoundException ex) {
return false;
} catch (LinkageError er) {
return false;
}
return true;
}
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
public boolean isAudioFocusSupported() {
return getApiLevel() >= 8;
return getApiLevel() >= 4;
}
// API Level 5 Changes
/**
* There's a bug (#1587) in Cupcake and Donut which prevents you from
* using a SQLiteQueryBuilder twice. That is, if you call buildQuery
@@ -121,9 +104,43 @@ public class ApiFeatures {
* up. Specifically, it'll add extra parens which don't belong.
*/
public boolean canReuseSQLiteQueryBuilder() {
return getApiLevel() > 4;
return getApiLevel() >= 5;
}
// API Level 8 Changes
public boolean isAudioFocusSupported() {
return getApiLevel() >= 8;
}
/**
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
*/
public boolean hasBackup() {
return getApiLevel() >= 8;
}
// API Level 9 Changes
/**
* Returns true if {@link NetHttpTransport} should be used rather than
* {@link ApacheHttpTransport}. See {@link HttpTransport} for more info.
*/
public boolean useNetHttpTransport() {
return getApiLevel() >= 9;
}
// API Level 10 changes
/**
* Returns true if
* {@link BluetoothDevice#createInsecureRfcommSocketToServiceRecord} is
* available.
*/
public boolean hasBluetoothDeviceCreateInsecureRfcommSocketToServiceRecord() {
return getApiLevel() >= 10;
}
// Visible for testing.
protected int getApiLevel() {
return ANDROID_API_LEVEL;
@@ -20,6 +20,9 @@ import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
* A set of methods that may be implemented in a platform specific way.
*
@@ -60,4 +63,25 @@ public interface ApiPlatformAdapter {
* Enables strict mode where supported, only if this is a development build.
*/
void enableStrictMode();
/**
* Copies elements from the input byte array into a new byte array, from
* indexes start (inclusive) to end (exclusive). The end index must be less
* than or equal to input.length.
*
* @param input the input byte array
* @param start the start index
* @param end the end index
* @return a new array containing elements from the input byte array
*/
byte[] copyByteArray(byte[] input, int start, int end);
/**
* Gets an instance of {@link DecimalFormatSymbols} for a locale.
*
* @param locale the locale
* @return a new instance of {@link DecimalFormatSymbols}
*/
DecimalFormatSymbols getDecimalFormatSymbols(Locale locale);
}
@@ -25,6 +25,8 @@ import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
* The Cupcake (API level 3) specific implementation of the
@@ -79,4 +81,17 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
public void enableStrictMode() {
// Not supported
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
int length = end - start;
byte[] output = new byte[length];
System.arraycopy(input, start, output, 0, length);
return output;
}
@Override
public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
return new DecimalFormatSymbols(locale);
}
}
@@ -18,7 +18,6 @@ package com.google.android.apps.mytracks.util;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.SharedPreferences.Editor;
/**
* The Eclair (API level 5) specific implementation of the
@@ -26,7 +25,7 @@ import android.content.SharedPreferences.Editor;
*
* @author Bartlomiej Niechwiej
*/
public class EclairPlatformAdapter implements ApiPlatformAdapter {
public class EclairPlatformAdapter extends CupcakePlatformAdapter {
@Override
public void startForeground(Service service,
@@ -40,14 +39,4 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
NotificationManager notificationManager, int id) {
service.stopForeground(id != -1);
}
@Override
public void applyPreferenceChanges(Editor editor) {
editor.commit();
}
@Override
public void enableStrictMode() {
// Not supported
}
}
@@ -6,6 +6,10 @@ import android.content.SharedPreferences.Editor;
import android.os.StrictMode;
import android.util.Log;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import java.util.Locale;
/**
* The Gingerbread (API level 9) specific implememntation of the
* {@link ApiPlatformAdapter}.
@@ -13,6 +17,7 @@ import android.util.Log;
* @author Rodrigo Damazio
*/
public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
@Override
public void applyPreferenceChanges(Editor editor) {
// Apply asynchronously
@@ -32,4 +37,14 @@ public class GingerbreadPlatformAdapter extends EclairPlatformAdapter {
.penaltyLog()
.build());
}
@Override
public byte[] copyByteArray(byte[] input, int start, int end) {
return Arrays.copyOfRange(input, start, end);
}
@Override
public DecimalFormatSymbols getDecimalFormatSymbols(Locale locale) {
return DecimalFormatSymbols.getInstance(locale);
}
}
@@ -28,7 +28,8 @@ public abstract class UnitConversions {
public static final double MI_TO_FEET = 5280.0;
public static final double KMH_TO_MPH = 1000 * M_TO_FT / MI_TO_FEET;
public static final double TO_RADIANS = Math.PI / 180.0;
public static final double MPH_TO_KMH = 1.609344;
protected UnitConversions() {
}
}
@@ -19,6 +19,16 @@ package com.google.android.apps.mytracks.widgets;
import static com.google.android.apps.mytracks.Constants.SETTINGS_NAME;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.services.ControlRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -32,16 +42,6 @@ import android.os.Handler;
import android.util.Log;
import android.widget.RemoteViews;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
/**
* An AppWidgetProvider for displaying key track statistics (distance, time,
* speed) from the current or most recent track.
@@ -175,14 +175,12 @@ public class TrackWidgetProvider
// If a new track is started by this appwidget or elsewhere,
// toggle the button to active and have it disable the track if pressed.
setButtonIntent(
views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled,
-1);
views, context, R.string.end_current_track_action, R.drawable.appwidget_button_enabled);
} else {
// If a track is stopped by this appwidget or elsewhere,
// toggle the button to inactive and have it start a new track if pressed.
setButtonIntent(
views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled,
R.string.select_new_track_extra);
views, context, R.string.start_new_track_action, R.drawable.appwidget_button_disabled);
}
}
@@ -193,15 +191,11 @@ public class TrackWidgetProvider
* @param context The widget context
* @param action The resource id of the action to fire when the button is pressed
* @param icon The resource id of the icon to show for the button
* @param extra Optional resource id of a boolean extra on the intent
*/
private void setButtonIntent(
RemoteViews views, Context context, int action, int icon, int extra) {
Intent intent = new Intent(context, TrackRecordingService.class);
RemoteViews views, Context context, int action, int icon) {
Intent intent = new Intent(context, ControlRecordingService.class);
intent.setAction(context.getString(action));
if (extra != -1) {
intent.putExtra(context.getString(extra), true);
}
PendingIntent pendingIntent = PendingIntent.getService(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.appwidget_button, pendingIntent);
+1
View File
@@ -50,4 +50,5 @@ message SensorDataSet {
optional SensorData heart_rate = 2;
optional SensorData cadence = 3;
optional SensorData power = 4;
optional SensorData battery_level = 5;
}
-11
View File
@@ -23,20 +23,9 @@ limitations under the License.
<!-- Broadcast notification Intent constants -->
<string name="track_started_broadcast_action">com.google.android.apps.mytracks.TRACK_STARTED</string>
<string name="track_paused_broadcast_action">com.google.android.apps.mytracks.TRACK_PAUSED</string>
<string name="track_resumed_broadcast_action">com.google.android.apps.mytracks.TRACK_RESUMED</string>
<string name="track_stopped_broadcast_action">com.google.android.apps.mytracks.TRACK_STOPPED</string>
<string name="track_id_broadcast_extra">com.google.android.apps.mytracks.TRACK_ID</string>
<!-- TrackRecordingService actions -->
<string name="start_new_track_action">com.google.android.apps.mytracks.START_NEW_TRACK</string>
<!--
Setting this intent makes the new track the selected track.
This is not expected to work if the my tracks activity is currently active.
-->
<string name="select_new_track_extra">com.google.android.apps.mytracks.SELECT_NEW_TRACK</string>
<string name="end_current_track_action">com.google.android.apps.mytracks.END_CURRENT_TRACK</string>
<!-- My Tracks service constants -->
<string name="mytracks_service_class">com.google.android.apps.mytracks.services.TrackRecordingService</string>
<string name="mytracks_service_package">com.google.android.maps.mytracks</string>
@@ -398,10 +398,18 @@ public final class Sensor {
public boolean hasPower() { return hasPower; }
public com.google.android.apps.mytracks.content.Sensor.SensorData getPower() { return power_; }
// optional .com.google.android.apps.mytracks.content.SensorData battery_level = 5;
public static final int BATTERY_LEVEL_FIELD_NUMBER = 5;
private boolean hasBatteryLevel;
private com.google.android.apps.mytracks.content.Sensor.SensorData batteryLevel_;
public boolean hasBatteryLevel() { return hasBatteryLevel; }
public com.google.android.apps.mytracks.content.Sensor.SensorData getBatteryLevel() { return batteryLevel_; }
private void initFields() {
heartRate_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
cadence_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
power_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
batteryLevel_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
}
public final boolean isInitialized() {
if (hasHeartRate()) {
@@ -413,6 +421,9 @@ public final class Sensor {
if (hasPower()) {
if (!getPower().isInitialized()) return false;
}
if (hasBatteryLevel()) {
if (!getBatteryLevel().isInitialized()) return false;
}
return true;
}
@@ -431,6 +442,9 @@ public final class Sensor {
if (hasPower()) {
output.writeMessage(4, getPower());
}
if (hasBatteryLevel()) {
output.writeMessage(5, getBatteryLevel());
}
}
private int memoizedSerializedSize = -1;
@@ -455,6 +469,10 @@ public final class Sensor {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(4, getPower());
}
if (hasBatteryLevel()) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(5, getBatteryLevel());
}
memoizedSerializedSize = size;
return size;
}
@@ -611,6 +629,9 @@ public final class Sensor {
if (other.hasPower()) {
mergePower(other.getPower());
}
if (other.hasBatteryLevel()) {
mergeBatteryLevel(other.getBatteryLevel());
}
return this;
}
@@ -660,6 +681,15 @@ public final class Sensor {
setPower(subBuilder.buildPartial());
break;
}
case 42: {
com.google.android.apps.mytracks.content.Sensor.SensorData.Builder subBuilder = com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder();
if (hasBatteryLevel()) {
subBuilder.mergeFrom(getBatteryLevel());
}
input.readMessage(subBuilder, extensionRegistry);
setBatteryLevel(subBuilder.buildPartial());
break;
}
}
}
}
@@ -794,6 +824,43 @@ public final class Sensor {
return this;
}
// optional .com.google.android.apps.mytracks.content.SensorData battery_level = 5;
public boolean hasBatteryLevel() {
return result.hasBatteryLevel();
}
public com.google.android.apps.mytracks.content.Sensor.SensorData getBatteryLevel() {
return result.getBatteryLevel();
}
public Builder setBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
if (value == null) {
throw new NullPointerException();
}
result.hasBatteryLevel = true;
result.batteryLevel_ = value;
return this;
}
public Builder setBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData.Builder builderForValue) {
result.hasBatteryLevel = true;
result.batteryLevel_ = builderForValue.build();
return this;
}
public Builder mergeBatteryLevel(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
if (result.hasBatteryLevel() &&
result.batteryLevel_ != com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) {
result.batteryLevel_ =
com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder(result.batteryLevel_).mergeFrom(value).buildPartial();
} else {
result.batteryLevel_ = value;
}
result.hasBatteryLevel = true;
return this;
}
public Builder clearBatteryLevel() {
result.hasBatteryLevel = false;
result.batteryLevel_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
return this;
}
// @@protoc_insertion_point(builder_scope:com.google.android.apps.mytracks.content.SensorDataSet)
}
@@ -16,115 +16,46 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.maps.GeoPoint;
import com.google.android.apps.mytracks.maps.SingleColorTrackPathPainter;
import com.google.android.maps.MapView;
import com.google.android.maps.Projection;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.location.Location;
import android.test.AndroidTestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Tests for the MyTracks map overlay.
*
* @author Bartlomiej Niechwiej
* @author Vangelis S.
*/
public class MapOverlayTest extends AndroidTestCase {
private Canvas canvas;
private MockMyTracksOverlay myTracksOverlay;
private MapView mockView;
private Projection mockProjection;
/**
* A mock version of {@code MapOverlay} that does not use
* {@class MapView}.
*/
private class MockMyTracksOverlay extends MapOverlay {
public MockMyTracksOverlay(Context context) {
super(context);
}
@Override
Projection getMapProjection(MapView mapView) {
return mockProjection;
}
@Override
Rect getMapViewRect(MapView mapView) {
return new Rect(0, 0, 100, 100);
}
@Override
Path newPath() {
return new MockPath();
}
}
/**
* A mock class that intercepts {@code Path}'s and records calls to
* {@code #moveTo()} and {@code #lineTo()}.
*/
private static class MockPath extends Path {
/** A list of disjoined path segments. */
public final List<List<PointF>> segments = new LinkedList<List<PointF>>();
/** The total number of points in this path. */
public int totalPoints;
private List<PointF> currentSegment;
@Override
public void lineTo(float x, float y) {
super.lineTo(x, y);
assertNotNull(currentSegment);
currentSegment.add(new PointF(x, y));
totalPoints++;
}
@Override
public void moveTo(float x, float y) {
super.moveTo(x, y);
segments.add(currentSegment =
new ArrayList<PointF>(Arrays.asList(new PointF(x, y))));
totalPoints++;
}
}
/**
* A mock {@code Projection} that acts as the identity matrix.
*/
private static class MockProjection implements Projection {
@Override
public Point toPixels(GeoPoint in, Point out) {
return out;
}
@Override
public float metersToEquatorPixels(float meters) {
return meters;
}
@Override
public GeoPoint fromPixels(int x, int y) {
return new GeoPoint(y, x);
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
canvas = new Canvas();
myTracksOverlay = new MockMyTracksOverlay(getContext());
// Enable drawing.
myTracksOverlay.setTrackDrawingEnabled(true);
mockView = null;
mockProjection = new MockProjection();
}
public void testAddLocation() throws Exception {
// Set a TrackPathPainter with a MockPath.
myTracksOverlay.setTrackPathPainter(new SingleColorTrackPathPainter(getContext()) {
@Override
public Path newPath() {
return new MockPath();
}
});
mockView = null;
}
public void testAddLocation() throws Exception {
Location location = new Location("gps");
location.setLatitude(10);
location.setLongitude(20);
@@ -137,19 +68,21 @@ public class MapOverlayTest extends AndroidTestCase {
myTracksOverlay.addLocation(location);
assertEquals(2, myTracksOverlay.getNumLocations());
assertEquals(0, myTracksOverlay.getNumWaypoints());
assertNull(myTracksOverlay.getLastPath());
assertNull(myTracksOverlay.getTrackPathPainter().getLastPath());
// Draw and make sure that we don't lose any point.
myTracksOverlay.draw(canvas, mockView, false);
assertEquals(2, myTracksOverlay.getNumLocations());
assertEquals(0, myTracksOverlay.getNumWaypoints());
assertNotNull(myTracksOverlay.getLastPath());
assertEquals(2, ((MockPath)myTracksOverlay.getLastPath()).totalPoints);
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
assertTrue(myTracksOverlay.getTrackPathPainter().getLastPath() instanceof MockPath);
MockPath path = (MockPath) myTracksOverlay.getTrackPathPainter().getLastPath();
assertEquals(2, path.totalPoints);
myTracksOverlay.draw(canvas, mockView, true);
assertEquals(2, myTracksOverlay.getNumLocations());
assertEquals(0, myTracksOverlay.getNumWaypoints());
assertNotNull(myTracksOverlay.getLastPath());
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
}
public void testClearPoints() throws Exception {
@@ -182,7 +115,7 @@ public class MapOverlayTest extends AndroidTestCase {
myTracksOverlay.addWaypoint(waypoint);
assertEquals(1, myTracksOverlay.getNumWaypoints());
assertEquals(0, myTracksOverlay.getNumLocations());
assertNull(myTracksOverlay.getLastPath());
assertNull(myTracksOverlay.getTrackPathPainter().getLastPath());
final int waypoints = 10;
for (int i = 0; i < waypoints; ++i) {
@@ -192,7 +125,7 @@ public class MapOverlayTest extends AndroidTestCase {
}
assertEquals(1 + waypoints, myTracksOverlay.getNumWaypoints());
assertEquals(0, myTracksOverlay.getNumLocations());
assertNull(myTracksOverlay.getLastPath());
assertNull(myTracksOverlay.getTrackPathPainter().getLastPath());
}
public void testClearWaypoints() throws Exception {
@@ -226,15 +159,15 @@ public class MapOverlayTest extends AndroidTestCase {
// Shadow.
myTracksOverlay.draw(canvas, mockView, true);
// We don't expect to do anything if
assertNull(myTracksOverlay.getLastPath());
assertNull(myTracksOverlay.getTrackPathPainter().getLastPath());
assertEquals(40, myTracksOverlay.getNumWaypoints());
assertEquals(100, myTracksOverlay.getNumLocations());
// No shadow.
myTracksOverlay.draw(canvas, mockView, false);
assertNotNull(myTracksOverlay.getLastPath());
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
MockPath path = (MockPath) myTracksOverlay.getLastPath();
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
assertTrue(myTracksOverlay.getTrackPathPainter().getLastPath() instanceof MockPath);
MockPath path = (MockPath) myTracksOverlay.getTrackPathPainter().getLastPath();
assertEquals(40, myTracksOverlay.getNumWaypoints());
assertEquals(100, myTracksOverlay.getNumLocations());
assertEquals(100, path.totalPoints);
@@ -0,0 +1,51 @@
/*
* 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.maps.MapView;
import com.google.android.maps.Projection;
import android.content.Context;
import android.graphics.Rect;
/**
* Elements for Tests for the MyTracks map overlay.
*
* @author Bartlomiej Niechwiej
* @author Vangelis S.
*
* A mock version of {@code MapOverlay} that does not use
* {@class MapView}.
*/
public class MockMyTracksOverlay extends MapOverlay {
private Projection mockProjection;
public MockMyTracksOverlay(Context context) {
super(context);
mockProjection = new MockProjection();
}
@Override
public Projection getMapProjection(MapView mapView) {
return mockProjection;
}
@Override
public Rect getMapViewRect(MapView mapView) {
return new Rect(0, 0, 100, 100);
}
}
@@ -0,0 +1,60 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import android.graphics.Path;
import android.graphics.PointF;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import junit.framework.Assert;
/**
* Elements for Tests for the MyTracks map overlay.
*
* @author Bartlomiej Niechwiej
* @author Vangelis S.
*
* A mock class that intercepts {@code Path}'s and records calls to
* {@code #moveTo()} and {@code #lineTo()}.
*/
public class MockPath extends Path {
/** A list of disjoined path segments. */
public final List<List<PointF>> segments = new LinkedList<List<PointF>>();
/** The total number of points in this path. */
public int totalPoints;
private List<PointF> currentSegment;
@Override
public void lineTo(float x, float y) {
super.lineTo(x, y);
Assert.assertNotNull(currentSegment);
currentSegment.add(new PointF(x, y));
totalPoints++;
}
@Override
public void moveTo(float x, float y) {
super.moveTo(x, y);
segments.add(currentSegment =
new ArrayList<PointF>(Arrays.asList(new PointF(x, y))));
totalPoints++;
}
}
@@ -0,0 +1,47 @@
/*
* 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.maps.GeoPoint;
import com.google.android.maps.Projection;
import android.graphics.Point;
/**
* Elements for Tests for the MyTracks map overlay.
*
* @author Bartlomiej Niechwiej
* @author Vangelis S.
*
* A mock {@code Projection} that acts as the identity matrix.
*/
public class MockProjection implements Projection {
@Override
public Point toPixels(GeoPoint in, Point out) {
return out;
}
@Override
public float metersToEquatorPixels(float meters) {
return meters;
}
@Override
public GeoPoint fromPixels(int x, int y) {
return new GeoPoint(y, x);
}
}
@@ -49,7 +49,6 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
protected void tearDown() throws Exception {
clearSelectedAndRecordingTracks();
waitForIdle();
super.tearDown();
}
@@ -85,7 +84,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
// Simulate start with ACTION_VIEW intent.
Intent startIntent = new Intent();
startIntent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx"));
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx", getActivity().getFilesDir()));
// TODO: Add a valid GPX.
@@ -108,7 +107,7 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
// Simulate start with ACTION_VIEW intent.
Intent startIntent = new Intent();
startIntent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx"));
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx", getActivity().getFilesDir()));
startIntent.setData(uri);
setActivityIntent(startIntent);
@@ -168,8 +167,13 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
// properly saved.
// Simulate a click on Save button.
Button save = (Button) activity.findViewById(R.id.trackdetails_save);
save.performClick();
final Button save = (Button) activity.findViewById(R.id.trackdetails_save);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
save.performClick();
}
});
// Check the remaining properties.
recordingTrackId = awaitRecordingStatus(5000, false);
@@ -28,6 +28,8 @@ import android.test.mock.MockContext;
import android.test.mock.MockResources;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import junit.framework.TestCase;
@@ -55,13 +57,13 @@ public class DocsHelper_AddTrackRowTest extends TestCase {
StringWritingDocsHelper docsHelper = new StringWritingDocsHelper();
addTrackRow(docsHelper, false);
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
String expectedData =
"<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/2006/extended'>"
+ "<gsx:name><![CDATA[trackName]]></gsx:name>"
+ "<gsx:description><![CDATA[trackDescription]]></gsx:description>"
// We format the date here because we can't guarantee/force the timezone
+ "<gsx:date><![CDATA[" + String.format("%tc", TIME) + "]]></gsx:date>"
+ "<gsx:date><![CDATA[" + dateFormat.format(new Date(TIME)) + "]]></gsx:date>"
+ "<gsx:totaltime><![CDATA[0:00:05]]></gsx:totaltime>"
+ "<gsx:movingtime><![CDATA[0:00:04]]></gsx:movingtime>"
+ "<gsx:distance><![CDATA[12.43]]></gsx:distance>"
@@ -76,12 +78,12 @@ public class DocsHelper_AddTrackRowTest extends TestCase {
+ "<gsx:maxelevation><![CDATA[1,804]]></gsx:maxelevation>"
+ "<gsx:elevationunit><![CDATA[feet]]></gsx:elevationunit>"
+ "<gsx:map>"
+ "<![CDATA[http://maps.google.com/maps/ms?msa=0&msid=trackMapId]]>"
+ "<![CDATA[https://maps.google.com/maps/ms?msa=0&msid=trackMapId]]>"
+ "</gsx:map>"
+ "</entry>";
assertEquals(
"http://spreadsheets.google.com/feeds/list/ssid/wsid/private/full",
"https://spreadsheets.google.com/feeds/list/ssid/wsid/private/full",
docsHelper.writtenSheetUri);
assertEquals(expectedData, docsHelper.writtenData);
}
@@ -53,7 +53,7 @@ public class WriteProgressControllerTest extends ActivityInstrumentationTestCase
}
};
WriteProgressController controller = new WriteProgressController(
final WriteProgressController controller = new WriteProgressController(
getActivity(), mockWriter, SaveActivity.PROGRESS_DIALOG);
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
@Override
@@ -62,6 +62,22 @@ public class WriteProgressControllerTest extends ActivityInstrumentationTestCase
}
});
/*
* The WriteProgressController constructor calls the mockWriter's
* setOnCompletionListener method with a listener that dismisses the
* progress dialog. However, this unit test only tests the
* WriteProgressController and doesn't actually show any progress dialog.
* Thus after the WriteProgressController is setup, we need to call the
* mockWriter's setOnCompletionListener method again with an listener that
* doesn't dismiss dialog.
*/
mockWriter.setOnCompletionListener(new TrackWriter.OnCompletionListener() {
@Override
public void onComplete() {
controller.getOnCompletionListener().onComplete();
}
});
dialogRef.set(controller.createProgressDialog());
controller.startWrite();
@@ -69,7 +85,6 @@ public class WriteProgressControllerTest extends ActivityInstrumentationTestCase
// wait for the writer to finish
writerDone.acquire();
assertFalse(dialogRef.get().isShowing());
assertTrue(controllerDoneRef.get());
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import android.location.Location;
/**
* Tests for the MyTracks track path descriptors and painters.
*
* @author Vangelis S.
*/
public class TrackPathDescriptorDynamicSpeedTest extends TrackPathPainterTestCase {
public void testDynamicSpeedTrackPathDescriptor() throws Exception {
Location location = new Location("gps");
location.setLatitude(10);
for (int i = 0; i < 100; ++i) {
location = new Location("gps");
location.setLatitude(20 + i / 2);
location.setLongitude(150 - i);
myTracksOverlay.addLocation(location);
}
TrackPathPainter painter = new DynamicSpeedTrackPathPainter(
getContext(), new DynamicSpeedTrackPathDescriptor(getContext()));
myTracksOverlay.setTrackPathPainter(painter);
int startLocationIdx = 0;
Boolean alwaysVisible = true;
assertNotNull(painter);
painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
painter.drawTrack(canvas);
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import android.location.Location;
/**
* Tests for the MyTracks track path descriptors and painters.
*
* @author Vangelis S.
*/
public class TrackPathDescriptorFixedSpeedTest extends TrackPathPainterTestCase {
public void testFixedSpeedTrackPathDescriptor() throws Exception {
Location location = new Location("gps");
location.setLatitude(10);
for (int i = 0; i < 100; ++i) {
location = new Location("gps");
location.setLatitude(20 + i / 2);
location.setLongitude(150 - i);
myTracksOverlay.addLocation(location);
}
TrackPathPainter painter = new DynamicSpeedTrackPathPainter(
getContext(), new FixedSpeedTrackPathDescriptor(getContext()));
myTracksOverlay.setTrackPathPainter(painter);
int startLocationIdx = 0;
Boolean alwaysVisible = true;
assertNotNull(painter);
painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
painter.drawTrack(canvas);
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
/**
* Tests for the MyTracks track path painter factory.
*
* @author Vangelis S.
*/
public class TrackPathPainterFactoryTest extends TrackPathPainterTestCase {
public void testTrackPathPainterFactory() throws Exception {
Location location = new Location("gps");
location.setLatitude(10);
for (int i = 0; i < 100; ++i) {
location = new Location("gps");
location.setLatitude(20 + i / 2);
location.setLongitude(150 - i);
myTracksOverlay.addLocation(location);
}
Context context = getContext();
SharedPreferences prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (prefs == null) {
return;
}
testTrackPathPainterFactorySpecific(context, prefs, R.string.track_color_mode_none,
SingleColorTrackPathPainter.class);
testTrackPathPainterFactorySpecific(context, prefs, R.string.track_color_mode_fixed,
DynamicSpeedTrackPathPainter.class);
testTrackPathPainterFactorySpecific(context, prefs, R.string.track_color_mode_dynamic,
DynamicSpeedTrackPathPainter.class);
}
private <T> void testTrackPathPainterFactorySpecific(Context context, SharedPreferences prefs,
int track_color_mode, Class <?> c) {
prefs.edit().putString(context.getString(R.string.track_color_mode_key),
context.getString(track_color_mode)).commit();
int startLocationIdx = 0;
Boolean alwaysVisible = true;
TrackPathPainter painter = TrackPathPainterFactory.getTrackPathPainter(context);
myTracksOverlay.setTrackPathPainter(painter);
assertNotNull(painter);
assertTrue(c.isInstance(painter));
painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
painter.drawTrack(canvas);
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.maps;
import android.location.Location;
/**
* Tests for the MyTracks track path descriptors and painters.
*
* @author Vangelis S.
*/
public class TrackPathPainterSingleColorTest extends TrackPathPainterTestCase {
public void testSimpeColorTrackPathPainter() throws Exception {
Location location = new Location("gps");
location.setLatitude(10);
for (int i = 0; i < 100; ++i) {
location = new Location("gps");
location.setLatitude(20 + i / 2);
location.setLongitude(150 - i);
myTracksOverlay.addLocation(location);
}
TrackPathPainter painter = new SingleColorTrackPathPainter(getContext());
myTracksOverlay.setTrackPathPainter(painter);
int startLocationIdx = 0;
Boolean alwaysVisible = true;
assertNotNull(painter);
painter.updatePath(myTracksOverlay.getMapProjection(mockView),
myTracksOverlay.getMapViewRect(mockView), startLocationIdx, alwaysVisible,
myTracksOverlay.getPoints());
assertNotNull(myTracksOverlay.getTrackPathPainter().getLastPath());
painter.drawTrack(canvas);
}
}

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