diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 6b255feb1..4e258a5fd 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -147,17 +147,6 @@ limitations under the License.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -211,17 +189,6 @@ limitations under the License.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/drawable/left_arrow_stats_stateful.xml b/MyTracks/res/drawable/left_arrow_stats_stateful.xml
deleted file mode 100644
index 5b3169af4..000000000
--- a/MyTracks/res/drawable/left_arrow_stats_stateful.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/drawable/left_arrow_track_stateful.xml b/MyTracks/res/drawable/left_arrow_track_stateful.xml
deleted file mode 100644
index 3ae27009c..000000000
--- a/MyTracks/res/drawable/left_arrow_track_stateful.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/drawable/right_arrow_chart_stateful.xml b/MyTracks/res/drawable/right_arrow_chart_stateful.xml
deleted file mode 100644
index eeb6b33f8..000000000
--- a/MyTracks/res/drawable/right_arrow_chart_stateful.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/drawable/right_arrow_stats_stateful.xml b/MyTracks/res/drawable/right_arrow_stats_stateful.xml
deleted file mode 100644
index 3ade4f429..000000000
--- a/MyTracks/res/drawable/right_arrow_stats_stateful.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/drawable/right_arrow_track_stateful.xml b/MyTracks/res/drawable/right_arrow_track_stateful.xml
deleted file mode 100644
index 9e5c364fd..000000000
--- a/MyTracks/res/drawable/right_arrow_track_stateful.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MyTracks/res/layout/track_detail.xml b/MyTracks/res/layout/track_detail.xml
new file mode 100644
index 000000000..1226d9ec0
--- /dev/null
+++ b/MyTracks/res/layout/track_detail.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MyTracks/res/values/navigation_icons.xml b/MyTracks/res/values/navigation_icons.xml
deleted file mode 100644
index e641291c9..000000000
--- a/MyTracks/res/values/navigation_icons.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
- - @drawable/left_arrow_chart_stateful
- - @drawable/left_arrow_track_stateful
- - @drawable/left_arrow_stats_stateful
-
-
-
- - @drawable/right_arrow_stats_stateful
- - @drawable/right_arrow_chart_stateful
- - @drawable/right_arrow_track_stateful
-
-
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 6ea22d9ef..11e44dd8a 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -18,37 +18,27 @@ limitations under the License.
+ translation_description="In the 'About My Tracks' page, the copyright notice.">
Copyright 2009 Google Inc.
+ 'Terms of service' dialog.">
License
+ 'Privacy: m.google.com/privacy'.">
Privacy:
+ 'Support: mytracks.appspot.com'.">
Support:
+ 'Version: 1.1.11'.">
Version:
@@ -430,31 +420,23 @@ limitations under the License.
+ only choose one of the options.">
By distance
+ only choose one of the options.">
By time
+ axis.">
X axis
+ axis.">
Y axis
@@ -500,6 +482,13 @@ limitations under the License.
%2$d elevation in feet">
Elevation gain: %1$d m (%2$d ft)
+
+ Fastest pace: %1$.2f min/km (%2$.1f min/mi)
+
Accuracy (m)
@@ -561,13 +550,6 @@ limitations under the License.
%1$d grade in percentage">
Min grade: %1$d %%
-
- Min pace: %1$.2f min/km (%2$.1f min/mi)
-
+ translation_description="The title on a confirmation dialog asking the user if he is sure?">
Are you sure?
+ translation_description="The title on a progress dialog asking the user to wait.">
Please wait...
+ translation_description="The title on an alert dialog indicating success.">
Success
+ translation_description="In the main view, the message on the top asking the user to enable
+ GPS.">
Please tap here to go to \'Location & security\' settings & check
\'Use GPS satellites\'. Press the back button to return.
+ translation_description="On application start, message when there is no GPS on the device.">
No GPS is found on the device.
+ waiting for a better GPS fix.">
Waiting for a better GPS fix. Make sure the sky is visible.
@@ -772,8 +743,7 @@ limitations under the License.
translation_description="After importing all the tracks from the SD card, the alert message
indicating that no track is imported.
- %1$s: the SD card directory path. E.g., '/mnt/sdcard/MyTracks/gpx/'.
- Screenshot: import_no_file.png">
+ %1$s: the SD card directory path. E.g., '/mnt/sdcard/MyTracks/gpx/'.">
No GPX file found in %1$s. The SD card may be unreadable or the directory may not contain any
GPX file.
@@ -787,8 +757,7 @@ limitations under the License.
%1$d the number of successfully imported tracks. E.g., '1'.
%2$s the number of GPX files in the SD card. E.g., '1 GPX file' or '2 GPX files'.
- %3$s the SD card directory path. E.g., '/mnt/sdcard/MyTracks/gpx/'.
- Screenshot: import_success.png">
+ %3$s the SD card directory path. E.g., '/mnt/sdcard/MyTracks/gpx/'.">
My Tracks: Imported %1$d of %2$s from %3$s.
@@ -832,37 +801,27 @@ limitations under the License.
+ description.">
Marker description
+ the marker name.">
Marker name
+ marker type, including examples.">
Marker type (e.g. building, pothole, sight...)
+ text field when it is empty.">
Marker type
+ the page.">
Marker details
+ translation_description="When inserting a new marker, a message indicating success.">
A marker was inserted.
+ menu options below are for this marker.">
This marker
+ marker.">
Delete
+ deleted from the phone.">
The marker will be permanently deleted from the phone.
+ metadata (e.g., name, type, etc.).">
Edit
+ marker.">
Insert statistics
+ marker.">
Insert waypoint
+ the user he can press and hold a marker for more options.">
Pick a marker. Press & hold for more options.
+ on the map.">
Show on map
+ translation_description="When inserting a statistics marker, the default name of the marker.">
Statistics
+ translation_description="When inserting a waypoint marker, the default name of the marker.">
Waypoint
@@ -984,7 +921,7 @@ limitations under the License.
Edit
-
+
@@ -1071,7 +1008,7 @@ limitations under the License.
Share with friends...
@@ -1089,20 +1026,16 @@ limitations under the License.
Map.">
Map
-
+
+ indicating that GPS is not available and an approximate location is used instead.">
GPS is not available. An approximate location based on cell towers and WiFi networks is shown.
+ indicating that the current location cannot be determined.">
GPS is not available. Current location cannot be determined. Enable more location sources (see
\'Location & security\' settings) & make sure the sky is visible. Try again later.
@@ -1114,93 +1047,70 @@ limitations under the License.
+ application that can import the file to My Tracks.">
My Tracks Importer
+ Character limit: ~23 (1 line)">
Recording service
+ notifications, the permission description that the Android Market shows to the user.">
Allows the application to receive notifications when My Tracks starts or stops a recording.
+ notifications, the permission label that the Android Market shows to the user.">
receive My Tracks notifications
+ the permission description that the Android Market shows to the user.">
Allows the application to read My Tracks data.
+ the permission label that the Android Market shows to the user.">
read My Tracks data
+ Tracks, the permission description that the Android Market shows to the user.">
Allows the application to write data to My Tracks and start/stop My Tracks recording.
+ Tracks, the permission label that the Android Market shows to the user.">
write data to My Tracks and start/stop My Tracks recording
+ to choose the imperial units.">
Imperial
+ to choose the metric units.">
Metric
+ metric or imperial.">
Preferred units
+ export operation was canceled.">
The operation was canceled.
+ from the SD card.">
Finished reading from the SD card.
+ the SD card.">
Finished writing to the SD card.
@@ -1283,16 +1189,12 @@ limitations under the License.
Created by %1$sMy Tracks%2$s on Android.
+ translation_description="When sending to Google, the dialog title to choose a Google account.">
Choose account
+ checkbox option to send to Google Docs.">
Google Docs
+ existing map as opposed to create a new map.">
Use existing map
+ checkbox option to send to Google Fusion Tables.">
Google Fusion Tables
+ checkbox option to send to Google Maps.">
Google Maps
+ new map as opposed to use an existing map.">
Create new map
+ the bottom to share with friends the URL of the uploaded track.">
Share track URL
+ button on the bottom to send the track to the selected Google services.">
Send now
+ the result page.">
It may take a few minutes before new tracks appear.
+ Google Docs.">
Send to...
+ sensor. E.g, 'Battery level: 50 %'.">
Battery level
+ 'Cadence: 80 rpm'.">
Cadence
+ has connected.">
Connected
+ connecting.">
Connecting
+ has disconnected.">
Disconnected
+ rate sensor. E.g., 'Hear rate: 100 bpm'.">
Heart rate
+ sent a data. E.g., 'Last sensor time: 10:05 am'.">
Last sensor time
+ sensor. Power is the power output of the rider. E.g., 'Power: 50 W'.">
Power
+ sending data.">
Sending
@@ -1487,16 +1355,14 @@ limitations under the License.
translation_description="In the 'Settings' page, the option to change the data backup related
settings.
- Character limit: ~26 (1 line)
- Screenshot: settings.png">
+ Character limit: ~26 (1 line)">
Backup
+ Character limit: ~26 (1 line)">
Backup now
+ the SD card.">
Write a full copy of all your tracks and settings to the SD card.
+ Character limit: ~26 (1 line)">
Restore now
+ and it will overwrite all the current tracks and settings.">
Restoring a backup from the SD card will overwrite all your current tracks and settings.
+ message in the alert dialog informing the user that there is no backup to restore from.">
No backup found.
+ title in the picker dialog asking the user to select a backup.">
Select a backup to restore
+ the SD card.">
Restore all tracks and settings from the SD card.
+ Character limit: ~40 (1 line)">
SD card
+ Character limit: ~26 (1 line)">
Display
+ Character limit: ~23 (1 line)">
Use metric
+ checkbox when it is unchecked explaining using the imperial units to display distances.">
Use imperial units to display distances.
+ checkbox when it is checked explaining using the metric units to display distances.">
Use metric units to display distances.
+ Character limit: ~23 (1 line)">
Display speed
+ speed (e.g., 5 miles per hour).">
Display pace (e.g., hours/mile).
+ (e.g., 0.2 hours per mile).">
Display speed (e.g., miles/hour).
+ Character limit: ~23 (1 line)">
Color mode
+ Character limit: ~21 (1 line)">
Dynamic thresholds
+ Character limit: ~23 (1 line)">
Percentage
+ the medium speed range.">
Percentage from the average speed for the medium speed range.
+ Character limit: ~21 (1 line)">
Fixed thresholds
+ Character limit: ~23 (1 line)">
Medium speed
+ for the medium speed color.">
Medium speed top value.
+ Character limit: ~23 (1 line)">
Slow speed
+ the slow speed color.">
Slow speed top value.
+ Character limit: ~21 (1 line)">
Single color
+ slow, medium, and fast speed.">
The way to color a track based on slow, medium, and fast speed.
+ track recording.">
Not available while recording a track.
+ Character limit: ~26 (1 line)">
Recording
+ Character limit: ~23 (1 line)">
Auto-resume
+ recording will not be resumed after phone reboot.">
Maximum time after which a previously recording track will not be resumed after phone reboot.
+ Character limit: ~23 (1 line)">
Default activity
+ option explaining that it sets the default activity type for a new track.">
Default activity type for a new track.
+ Character limit: ~23 (1 line)">
Distance interval
+ recordings of your position.">
Smallest distance between two recordings of your position.
+ Character limit: ~23 (1 line)">
GPS accuracy
+ GPS position's accuracy is below the requirement, then it will not be used.">
GPS position will be ignored if it is not this accurate.
+ Character limit: ~23 (1 line)">
Time interval
+ recordings of your position.">
Smallest time interval between two recordings of your position.
+ Character limit: ~23 (1 line)">
Marker
+ will be inserted.">
Time/distance after which a new marker will be inserted.
+ Character limit: ~23 (1 line)">
Max interval
+ points for them to be still considered as the same track.">
Maximum distance between two recordings of your position. If two positions are further than
this, the track will be split.
@@ -1879,23 +1683,19 @@ limitations under the License.
translation_description="In the 'Recording' settings, the dropdown option to configure the voice
announcement frequency.
- Character limit: ~23 (1 line)
- Screenshot: settings_recording_top.png">
+ Character limit: ~23 (1 line)">
Voice
+ announcements.">
Time/distance between two voice announcements.
+ Character limit: ~26 (1 line)">
Reset
+ defaults.">
All settings have been reverted to their defaults.
+ explaining reverting all the settings to the default values.">
Revert all your settings to the default values.
+ Character limit: ~26 (1 line)">
Sensor
+ sensor' or 'Unpair SRM ANT+ bridge' when there no ANT+ device paired.">
Not paired.
+ Character limit: ~40 (1 line)">
ANT+ options
+ %1$d: sensor id number. E.g., '1'.">
Paired with: %1$d.
+ Character limit: ~40 (1 line)">
Bluetooth options
+ Character limit: ~26 (1 line)">
Bluetooth pairing
+ Bluetooth settings to pair a device.">
Open Android\'s Bluetooth settings to pair a device.
+ Character limit: ~23 (1 line)">
Bluetooth sensor
+ sensor to connect to.">
The Bluetooth sensor to connect to.
+ Character limit: ~23 (1 line)">
Sensor type
+ Character limit: ~21 (1 line)">
ANT+ heart rate
+ Character limit: ~21 (1 line)">
None
+ Character limit: ~21 (1 line)">
Polar Bluetooth
+ Character limit: ~21 (1 line)">
SRM ANT+ bridge
+ explaining that it is to configure the type of sensor to connect to.">
The type of sensor to connect to.
+ Character limit: ~21 (1 line)">
Zephyr Bluetooth
+ Character limit: ~26 (1 line)">
Unpair ANT+ heart rate
+ Character limit: ~26 (1 line)">
Unpair SRM ANT+ bridge
+ Character limit: ~26 (1 line)">
Sharing
+ Character limit: ~23 (1 line)">
Allow access
+ recording My Tracks data.">
Other apps cannot read, write, start/stop recording My Tracks data.
+ Tracks data.">
Other apps can read, write, start/stop recording My Tracks data.
+ Character limit: ~23 (1 line)">
New maps public
+ be unlisted, as opposed to public.">
Make new maps created in Google Maps unlisted.
+ be public, as opposed to unlisted.">
Make new maps created in Google Maps public.
+ Character limit: ~23 (1 line)">
Share only URL
+ sharing a URL with friends.">
Include descriptive text when sharing a URL with friends.
+ sharing a URL with friends.">
Remove any descriptive text when sharing a URL with friends.
+ translation_description="The title of the 'Settings' page.">
My Tracks settings
+ translation_description="When sharing a track file with friends, the body of the message.">
I think you might be interested in this track.\n\nTo view this track use Google Earth or other
mapping software.
+ the user which application he wants to use.">
Share track using:
+ translation_description="When sharing a track with friends, the subject line.">
I would like to share a track with you
+ %1$s: url. E.g, 'https://maps.google.com/maps'.">
I think you might be interested in this track:\n\n%1$s
+ translation_description="In the 'Statistics' page, the average moving pace.">
Average moving pace
+ translation_description="In the 'Statistics' page, the average moving speed.">
Average moving speed
+ translation_description="In the 'Statistics' page, the average pace.">
Average pace
+ translation_description="In the 'Statistics' page, the average speed.">
Average speed
+ translation_description="In the 'Statistics' page, the elevation.">
Elevation
+ translation_description="In the 'Statistics' page, the elevation gain.">
Elevation gain
+
+ Fastest pace
+
+ translation_description="In the 'Statistics' page, the latitude.">
Latitude
+ translation_description="In the 'Statistics' page, the longitude.">
Longitude
+ translation_description="In the 'Statistics' page, the maximum elevation.">
Max elevation
+ translation_description="In the 'Statistics' page, the maximum grade.">
Max grade
+ translation_description="In the 'Statistics' page, the maximum speed.">
Max speed
+ translation_description="In the 'Statistics' page, the minimum elevation.">
Min elevation
+ translation_description="In the 'Statistics' page, the minimum grade.">
Min grade
-
- Min pace
-
+ translation_description="In the 'Statistics' page, the moving time.">
Moving time
+ translation_description="In the 'Statistics' page, the pace like hours per mile.">
Pace
+ translation_description="In the 'Statistics' page, the speed like miles per hour.">
Speed
+ translation_description="In the 'Statistics' page, the total distance.">
Total distance
+ translation_description="In the 'Statistics' page, the total time.">
Total time
+
+ Chart
+
@@ -2316,6 +2031,14 @@ limitations under the License.
Play Store.">
You need Google Earth to play the track. Do you want to install from the Play Store?
+
+ Map
+
+
+ Stats
+
Track
@@ -2407,21 +2130,15 @@ limitations under the License.
+ translation_description="In a setting picker, the value to adapt for accuracy.">
Adapt for accuracy
+ translation_description="In a setting picker, the value to adapt for battery life.">
Adapt for battery life
+ translation_description="In a setting picker, the value to always enable the option.">
Always
+ %1$.1f: a float number. E.g., '2.2'">
%1$.1f mi (poor GPS)
+ %1$d: an integer value. E.g., '2'">
%1$d ft
+ %1$d: an integer value. E.g., '2'">
%1$d ft (excellent GPS)
+ %1$d: an integer value. E.g., '2'">
%1$d ft (recommended)
+ %1$d: an integer value. E.g., '2'">
%1$d km
+ %1$d: an integer value. E.g., '2'">
%1$d m
+ %1$d: an integer value. E.g., '2'">
%1$d m (excellent GPS)
+ %1$d: an integer value. E.g., '2'">
%1$d m (poor GPS)
+ %1$d: an integer value. E.g., '2'">
%1$d m (recommended)
+ %1$d: an integer value. E.g., '2'">
%1$d mi
+ %1$d: an integer value. E.g., '2'">
%1$d min
+ %1$d: an integer value. E.g., '2'">
%1$d s
+ translation_description="In a setting picker, the value to never use the feature.">
Never
+ translation_description="In a setting picker, the value to turn off the feature.">
Off
+ recommended value.">
Smallest (recommended)
@@ -3096,42 +2795,30 @@ limitations under the License.
+ Tracks' dialog.">
About My Tracks
+ graphs view.">
In the graphs view, Menu -> Chart settings.
+ the graphs view.">
How do I add more graphs?
+ translation_description="In the 'Welcome' page, the help content answer on app navigation.">
Touch the map. Left and right arrows will appear. Tap on these arrows to cycle between the map,
statistics, and graphs view.
+ translation_description="In the 'Welcome' page, the help content question on app navigation.">
How do I see the map, statistics, and graphs view?
+ translation_description="In the 'Welcome' page, the help content answer on battery life.">
The GPS receiver draws a relatively high amount of power, so a fully charged battery typically
survives less than 5 hours. Battery life depends on many factors, including signal strength,
battery freshness, and tasks running in parallel. Switching off some or all of the phone\'s
@@ -3139,58 +2826,44 @@ limitations under the License.
results.
+ translation_description="In the 'Welcome' page, the help content question on battery life.">
How long does the battery last when recording tracks?
+ translation_description="In the 'Welcome' page, the help content answer on data connection.">
Cell signal or data connection is not required to record GPS tracks. However, if there is no
data connection, you will not be able to see a map. Instead, you will see a grey screen or a
partial map. Tracks, represented as simple lines, can still be recorded even if there is no data
connection.
+ translation_description="In the 'Welcome' page, the help content question on data connection.">
Do I need cell signal and data connection to use My Tracks?
+ translation_description="In the 'Welcome' page, the help content answer on deleting all
+ tracks.">
Menu -> Tracks -> Delete all. This permanently deletes the recorded tracks from memory. If you
have saved tracks to the SD card, you must delete them separately. Tracks uploaded to Google
Maps, Google Fusion Tables, and Google Docs must also be deleted separately.
+ tracks.">
How do I delete all the tracks from the phone?
+ translation_description="In the 'Welcome' page, the help content answer on editing points.">
Once you upload tracks to Google Maps, Google Fusion Tables, or Google Docs, you can move,
delete or insert individual track points as desired.
+ translation_description="In the 'Welcome' page, the help content question on editing points.">
How can I edit or correct tracks for accuracy?
+ translation_description="In the 'Welcome' page, the help content answer on elevation
+ precision.">
GPS receivers measure elevations much less precisely than latitude and longitude. Elevations
can sometimes be off by a few hundred feet, though relative changes are tracked more precisely.
To get a good elevation measurement, make sure that you have a strong GPS signal and wait a few
@@ -3198,15 +2871,12 @@ limitations under the License.
+ precision.">
How precise is the elevation measurement?
+ translation_description="In the 'Welcome' page, the help content answer on the GPS fix
+ message.">
For My Tracks to work, your phone needs to be in line-of-sight contact with the GPS satellites.
If the line-of-sight is obstructed by trees, buildings or canyon walls, you may not get an
accurate fix. If you see the message, wait a minute or two. If your device does not achieve a
@@ -3214,16 +2884,12 @@ limitations under the License.
+ message.">
Why does the application sometimes display \"Waiting for a better GPS fix. Make sure the sky is
visible.\"?
+ translation_description="In the 'Welcome' page, the help content answer on location precision.">
Under optimal conditions, the location error can be +/- 20 feet (6 meters). However, precision
depends on the quality of the GPS signal. The blue circle around the orange location arrow
indicates estimated precision. (You may need to zoom in or out to see the circle.) You can
@@ -3231,74 +2897,56 @@ limitations under the License.
at the top of the screen, you will see the message \"Waiting for a better GPS fix.\".
+ translation_description="In the 'Welcome' page, the help content question on location
+ precision.">
How precise are the recorded tracks?
+ visibility.">
By default, new maps are public. To change the default to unlisted, uncheck Settings -> Sharing
-> Make new maps public. You can change the visibility of all your maps at My places in Google
Maps.
+ visibility.">
Who can see the maps I send to Google Maps?
+ translation_description="In the 'Welcome' page, the help content answer on what is My Tracks.">
My Tracks allows you to record GPS tracks and visualize your hiking, running, biking or any
other activity while watching live performance statistics. My Tracks makes it easy to archive
your training history with Google Maps, Google Fusion Tables, and Google Docs, and share your
activities with friends and the world.
+ translation_description="In the 'Welcome' page, the help content question on what is My
+ Tracks.">
What is My Tracks?
+ Google.">
Tap the Options button located at the bottom right corner of the map view and select \"Send to
Google...\".
+ Google.">
How do I send my tracks to Google Maps, Google Fusion Tables, and Google Docs?
+ translation_description="In the 'Welcome' page, the help content answer on sharing a file.">
You can use Google Earth or similar applications to visualize and edit GPS tracks.
+ translation_description="In the 'Welcome' page, the help content question on sharing a file.">
What can I do with tracks after saving them to the SD card or sharing such files with friends?
+ friends.">
Tap the Options button located at the bottom right corner of the map view and select \"Share
with friends...\". You can share the track as a link to Google Map or Google Fusion Table
(you will be prompted to upload the track first) or as a GPX, KML, CSV, or TCX file in an e-mail
@@ -3306,58 +2954,43 @@ limitations under the License.
+ with friends.">
How do I share tracks with friends and the rest of the world?
+ tracks.">
Tracks on Google Maps and Google Fusion Tables are limited to certain sizes.
+ tracks.">
Why do very long tracks split into multiple segments upon sending them to Google Maps and
Google Fusion Tables?
+ translation_description="In the 'Welcome' page, the help content answer on statistics markers.">
Statistics markers are used to segment a track to calculate statistics for each segment. For
example, let\'s say you are recording a bike ride up a hill, you can insert a statistics marker
at the bottom of the hill and another at the top by pressing Menu -> Markers -> Insert
statistics.
+ translation_description="In the 'Welcome' page, the help content question on statistics
+ markers.">
What are statistics markers?
+ translation_description="In the 'Welcome' page, the title of the page.">
My Tracks Welcome
+ translation_description="In the 'Welcome' page, the help content answer on waypoint markers.">
Waypoint markers are used to annotate a location. For example, if you are recording a track and
you are close to an interesting place, press Menu -> Markers -> Insert waypoint.
+ translation_description="In the 'Welcome' page, the help content question on waypoint markers.">
What are waypoint markers?
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
deleted file mode 100644
index 265f6e4d8..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartActivity.java
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Copyright 2009 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.google.android.apps.mytracks;
-
-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.Sensor.SensorDataSet;
-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.stats.DoubleBuffer;
-import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
-import com.google.android.apps.mytracks.util.LocationUtils;
-import com.google.android.apps.mytracks.util.UnitConversions;
-import com.google.android.maps.mytracks.R;
-import com.google.common.annotations.VisibleForTesting;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.location.Location;
-import android.os.Bundle;
-import android.speech.tts.TextToSpeech;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.CheckBox;
-import android.widget.LinearLayout;
-import android.widget.RadioGroup;
-import android.widget.ZoomControls;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-
-/**
- * An activity that displays a chart from the track point provider.
- *
- * @author Sandor Dornbush
- * @author Rodrigo Damazio
- */
-public class ChartActivity extends Activity implements TrackDataListener {
-
- private static final int DIALOG_CHART_SETTINGS_ID = 0;
-
- private final DoubleBuffer elevationBuffer =
- new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
- private final DoubleBuffer speedBuffer =
- new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
- private final ArrayList pendingPoints = new ArrayList();
-
- private TrackDataHub dataHub;
-
- // Stats gathered from received data.
- private double profileLength = 0;
- private long startTime = -1;
- private Location lastLocation;
- private double trackMaxSpeed;
-
- // Modes of operation
- private boolean metricUnits = true;
- private boolean reportSpeed = true;
-
- /*
- * UI elements:
- */
- private ChartView chartView;
- private LinearLayout busyPane;
- private ZoomControls zoomControls;
-
- /**
- * A runnable that can be posted to the UI thread. It will remove the spinner
- * (if any), enable/disable zoom controls and orange pointer as appropriate
- * and redraw.
- */
- private final Runnable updateChart = new Runnable() {
- @Override
- public void run() {
- // Get a local reference in case it's set to null concurrently with this.
- TrackDataHub localDataHub = dataHub;
- if (localDataHub == null || isFinishing()) {
- return;
- }
-
- busyPane.setVisibility(View.GONE);
- zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
- zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
- chartView.setShowPointer(localDataHub.isRecordingSelected());
- chartView.invalidate();
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Log.w(TAG, "ChartActivity.onCreate");
- super.onCreate(savedInstanceState);
-
- // The volume we want to control is the Text-To-Speech volume
- setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
-
- setContentView(R.layout.mytracks_charts);
- ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
- chartView = new ChartView(this);
- LayoutParams params =
- new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- layout.addView(chartView, params);
-
- busyPane = (LinearLayout) findViewById(R.id.elevation_busypane);
- zoomControls = (ZoomControls) findViewById(R.id.elevation_zoom);
- zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- zoomIn();
- }
- });
- zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- zoomOut();
- }
- });
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
- dataHub.registerTrackDataListener(this, EnumSet.of(
- ListenerDataType.SELECTED_TRACK_CHANGED,
- ListenerDataType.TRACK_UPDATES,
- ListenerDataType.POINT_UPDATES,
- ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
- ListenerDataType.WAYPOINT_UPDATES,
- ListenerDataType.DISPLAY_PREFERENCES));
- }
-
- @Override
- protected void onPause() {
- dataHub.unregisterTrackDataListener(this);
- dataHub = null;
-
- super.onPause();
- }
-
- private void zoomIn() {
- chartView.zoomIn();
- zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
- zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
- }
-
- private void zoomOut() {
- chartView.zoomOut();
- zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
- zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
- }
-
- private void setMode(Mode newMode) {
- if (chartView.getMode() != newMode) {
- chartView.setMode(newMode);
- dataHub.reloadDataForListener(this);
- }
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- if (id != DIALOG_CHART_SETTINGS_ID) {
- return null;
- }
-
- View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
- final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
- radioGroup.check(chartView.getMode() == Mode.BY_DISTANCE
- ? R.id.chart_settings_by_distance : R.id.chart_settings_by_time);
-
- final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
- checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view.findViewById(
- R.id.chart_settings_elevation);
- checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
- checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
- checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view.findViewById(
- R.id.chart_settings_cadence);
- checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view.findViewById(
- R.id.chart_settings_heart_rate);
-
- // set checkboxes values
- for (int i = 0; i < ChartView.NUM_SERIES; i++) {
- checkBoxes[i].setChecked(chartView.isChartValueSeriesEnabled(i));
- }
- checkBoxes[ChartView.SPEED_SERIES].setText(reportSpeed
- ? R.string.stat_speed : R.string.stat_pace);
-
- return new AlertDialog.Builder(this)
- .setCancelable(true)
- .setNegativeButton(R.string.generic_cancel, null)
- .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance
- ? Mode.BY_DISTANCE
- : Mode.BY_TIME);
- for (int i = 0; i < ChartView.NUM_SERIES; i++) {
- chartView.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
- }
- chartView.postInvalidate();
- }
- })
- .setTitle(R.string.menu_chart_settings)
- .setView(view)
- .create();
- }
-
- /**
- * Shows the chart settings dialog
- */
- public void showChartSettingsDialog() {
- showDialog(DIALOG_CHART_SETTINGS_ID);
- }
-
- /**
- * Given a location, creates a new data point for the chart. A data point is
- * an array double[3 or 6], where:
- * data[0] = the time or distance
- * data[1] = the elevation
- * data[2] = the speed
- * and possibly:
- * data[3] = power
- * data[4] = cadence
- * data[5] = heart rate
- *
- * This must be called in order for each point.
- *
- * @param location the location to get data for (this method takes ownership of that location)
- * @param result the resulting point to fill out
- */
- @VisibleForTesting
- void fillDataPoint(Location location, double result[]) {
- double timeOrDistance = Double.NaN,
- elevation = Double.NaN,
- speed = Double.NaN,
- power = Double.NaN,
- cadence = Double.NaN,
- heartRate = Double.NaN;
-
- if (location instanceof MyTracksLocation &&
- ((MyTracksLocation) location).getSensorDataSet() != null) {
- SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet();
- if (sensorData.hasPower()
- && sensorData.getPower().getState() == Sensor.SensorState.SENDING
- && sensorData.getPower().hasValue()) {
- power = sensorData.getPower().getValue();
- }
- if (sensorData.hasCadence()
- && sensorData.getCadence().getState() == Sensor.SensorState.SENDING
- && sensorData.getCadence().hasValue()) {
- cadence = sensorData.getCadence().getValue();
- }
- if (sensorData.hasHeartRate()
- && sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
- && sensorData.getHeartRate().hasValue()) {
- heartRate = sensorData.getHeartRate().getValue();
- }
- }
-
- // TODO: Account for segment splits?
- Mode mode = chartView.getMode();
- switch (mode) {
- case BY_DISTANCE:
- if (lastLocation != null) {
- double d = lastLocation.distanceTo(location);
- if (metricUnits) {
- profileLength += d;
- } else {
- profileLength += d * UnitConversions.KM_TO_MI;
- }
- }
- timeOrDistance = profileLength * UnitConversions.M_TO_KM;
- break;
- case BY_TIME:
- if (startTime == -1) {
- // Base case
- startTime = location.getTime();
- }
- timeOrDistance = (location.getTime() - startTime);
- break;
- default:
- Log.w(TAG, "ChartActivity unknown mode: " + mode);
- }
-
- elevationBuffer.setNext(metricUnits
- ? location.getAltitude()
- : location.getAltitude() * UnitConversions.M_TO_FT);
- elevation = elevationBuffer.getAverage();
-
- if (lastLocation == null) {
- if (Math.abs(location.getSpeed() - 128) > 1) {
- speedBuffer.setNext(location.getSpeed());
- }
- } else if (TripStatisticsBuilder.isValidSpeed(
- location.getTime(), location.getSpeed(), lastLocation.getTime(),
- lastLocation.getSpeed(), speedBuffer)
- && (location.getSpeed() <= trackMaxSpeed)) {
- speedBuffer.setNext(location.getSpeed());
- }
- speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
- if (!metricUnits) {
- speed *= UnitConversions.KM_TO_MI;
- }
- if (!reportSpeed) {
- if (speed != 0) {
- // Format as hours per unit
- speed = (60.0 / speed);
- } else {
- speed = Double.NaN;
- }
- }
-
- // Keep a copy so the location can be reused.
- lastLocation = location;
-
- if (result != null) {
- result[0] = timeOrDistance;
- result[1] = elevation;
- result[2] = speed;
- result[3] = power;
- result[4] = cadence;
- result[5] = heartRate;
- }
- }
-
- @Override
- public void onProviderStateChange(ProviderState state) {
- // We don't care.
- }
-
- @Override
- public void onCurrentLocationChanged(Location loc) {
- // We don't care.
- }
-
- @Override
- public void onCurrentHeadingChanged(double heading) {
- // We don't care.
- }
-
- @Override
- public void onSelectedTrackChanged(Track track, boolean isRecording) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- busyPane.setVisibility(View.VISIBLE);
- }
- });
- }
-
- @Override
- public void onTrackUpdated(Track track) {
- if (track == null || track.getStatistics() == null) {
- trackMaxSpeed = 0.0;
- return;
- }
-
- trackMaxSpeed = track.getStatistics().getMaxSpeed();
- }
-
- @Override
- public void clearTrackPoints() {
- profileLength = 0;
- lastLocation = null;
- startTime = -1;
- elevationBuffer.reset();
- chartView.reset();
- speedBuffer.reset();
- pendingPoints.clear();
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- chartView.resetScroll();
- }
- });
- }
-
- @Override
- public void onNewTrackPoint(Location loc) {
- if (LocationUtils.isValidLocation(loc)) {
- double[] point = new double[6];
- fillDataPoint(loc, point);
- pendingPoints.add(point);
- }
- }
-
- @Override
- public void onSampledOutTrackPoint(Location loc) {
- // Still account for the point in the smoothing buffers.
- fillDataPoint(loc, null);
- }
-
- @Override
- public void onSegmentSplit() {
- // Do nothing.
- }
-
- @Override
- public void onNewTrackPointsDone() {
- chartView.addDataPoints(pendingPoints);
- pendingPoints.clear();
- runOnUiThread(updateChart);
- }
-
- @Override
- public void clearWaypoints() {
- chartView.clearWaypoints();
- }
-
- @Override
- public void onNewWaypoint(Waypoint wpt) {
- chartView.addWaypoint(wpt);
- }
-
- @Override
- public void onNewWaypointsDone() {
- runOnUiThread(updateChart);
- }
-
- @Override
- public boolean onUnitsChanged(boolean metric) {
- if (metricUnits == metric) {
- return false;
- }
- metricUnits = metric;
- chartView.setMetricUnits(metricUnits);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- chartView.requestLayout();
- }
- });
- return true;
- }
-
- @Override
- public boolean onReportSpeedChanged(boolean speed) {
- if (reportSpeed == speed) {
- return false;
- }
- reportSpeed = speed;
- chartView.setReportSpeed(speed, this);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- chartView.requestLayout();
- }
- });
- return true;
- }
-
- @VisibleForTesting
- ChartView getChartView() {
- return chartView;
- }
-
- @VisibleForTesting
- void setTrackMaxSpeed(double maxSpeed) {
- trackMaxSpeed = maxSpeed;
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java b/MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java
new file mode 100644
index 000000000..cc46dc13d
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartFragment.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.android.apps.mytracks;
+
+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.Sensor.SensorDataSet;
+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.stats.DoubleBuffer;
+import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
+import com.google.android.apps.mytracks.util.LocationUtils;
+import com.google.android.apps.mytracks.util.UnitConversions;
+import com.google.android.maps.mytracks.R;
+import com.google.common.annotations.VisibleForTesting;
+
+import android.location.Location;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.LinearLayout;
+import android.widget.ZoomControls;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+/**
+ * A fragment to display track chart to the user.
+ *
+ * @author Sandor Dornbush
+ * @author Rodrigo Damazio
+ */
+public class ChartFragment extends Fragment implements TrackDataListener {
+
+ // Android reports 128 when the speed is invalid
+ private static final int INVALID_SPEED = 128;
+ private final DoubleBuffer elevationBuffer = new DoubleBuffer(
+ Constants.ELEVATION_SMOOTHING_FACTOR);
+ private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
+ private final ArrayList pendingPoints = new ArrayList();
+
+ private TrackDataHub trackDataHub;
+
+ // Stats gathered from the received data
+ private double totalDistance = 0.0;
+ private long startTime = -1L;
+ private Location lastLocation = null;
+ private double trackMaxSpeed = 0.0;
+
+ // Modes of operation
+ private boolean metricUnits = true;
+ private boolean reportSpeed = true;
+
+ // UI elements
+ private ChartView chartView;
+ private LinearLayout busyPane;
+ private ZoomControls zoomControls;
+
+ /**
+ * A runnable that will remove the spinner (if any), enable/disable zoom
+ * controls and orange pointer as appropriate and redraw.
+ */
+ private final Runnable updateChart = new Runnable() {
+ @Override
+ public void run() {
+ if (trackDataHub == null) {
+ return;
+ }
+
+ busyPane.setVisibility(View.GONE);
+ zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
+ zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
+ chartView.setShowPointer(isRecording());
+ chartView.invalidate();
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ /*
+ * Create a chartView here to store data thus won't need to reload all the
+ * data on every onStart or onResume.
+ */
+ chartView = new ChartView(getActivity());
+ };
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.mytracks_charts, container, false);
+ busyPane = (LinearLayout) view.findViewById(R.id.elevation_busypane);
+ zoomControls = (ZoomControls) view.findViewById(R.id.elevation_zoom);
+ zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ zoomIn();
+ }
+ });
+ zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ zoomOut();
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
+ LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ layout.addView(chartView, layoutParams);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ resumeTrackDataHub();
+ getActivity().runOnUiThread(updateChart);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseTrackDataHub();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
+ layout.removeView(chartView);
+ }
+
+ /**
+ * Sets the chart view mode.
+ *
+ * @param mode the chart view mode
+ */
+ public void setMode(Mode mode) {
+ if (chartView.getMode() != mode) {
+ chartView.setMode(mode);
+ reloadTrackDataHub();
+ }
+ }
+
+ /**
+ * Gets the chart view mode.
+ */
+ public Mode getMode() {
+ return chartView.getMode();
+ }
+
+ /**
+ * Enables or disables the chart value series.
+ *
+ * @param index the index of the series
+ * @param enabled true to enable, false to disable
+ */
+ public void setChartValueSeriesEnabled(int index, boolean enabled) {
+ chartView.setChartValueSeriesEnabled(index, enabled);
+ }
+
+ /**
+ * Returns true if the chart value series is enabled.
+ *
+ * @param index the index of the series
+ */
+ public boolean isChartValueSeriesEnabled(int index) {
+ return chartView.isChartValueSeriesEnabled(index);
+ }
+
+ /**
+ * Returns true to report speed instead of pace.
+ */
+ public boolean isReportSpeed() {
+ return reportSpeed;
+ }
+
+ /**
+ * Updates the chart.
+ */
+ public void update() {
+ chartView.postInvalidate();
+ }
+
+ @Override
+ public void onProviderStateChange(ProviderState state) {
+ // We don't care.
+ }
+
+ @Override
+ public void onCurrentLocationChanged(Location loc) {
+ // We don't care.
+ }
+
+ @Override
+ public void onCurrentHeadingChanged(double heading) {
+ // We don't care.
+ }
+
+ @Override
+ public void onSelectedTrackChanged(Track track, boolean isRecording) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ busyPane.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ @Override
+ public void onTrackUpdated(Track track) {
+ if (track == null || track.getStatistics() == null) {
+ trackMaxSpeed = 0.0;
+ return;
+ }
+ trackMaxSpeed = track.getStatistics().getMaxSpeed();
+ }
+
+ @Override
+ public void clearTrackPoints() {
+ totalDistance = 0.0;
+ startTime = -1L;
+ lastLocation = null;
+
+ elevationBuffer.reset();
+ speedBuffer.reset();
+ pendingPoints.clear();
+
+ chartView.reset();
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ chartView.resetScroll();
+ }
+ });
+ }
+
+ @Override
+ public void onNewTrackPoint(Location location) {
+ if (LocationUtils.isValidLocation(location)) {
+ double[] data = new double[6];
+ fillDataPoint(location, data);
+ pendingPoints.add(data);
+ }
+ }
+
+ @Override
+ public void onSampledOutTrackPoint(Location location) {
+ if (LocationUtils.isValidLocation(location)) {
+ // Still account for the point in the smoothing buffers.
+ fillDataPoint(location, null);
+ }
+ }
+
+ @Override
+ public void onSegmentSplit() {
+ // Do nothing.
+ }
+
+ @Override
+ public void onNewTrackPointsDone() {
+ chartView.addDataPoints(pendingPoints);
+ pendingPoints.clear();
+ getActivity().runOnUiThread(updateChart);
+ }
+
+ @Override
+ public void clearWaypoints() {
+ chartView.clearWaypoints();
+ }
+
+ @Override
+ public void onNewWaypoint(Waypoint waypoint) {
+ if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
+ chartView.addWaypoint(waypoint);
+ }
+ }
+
+ @Override
+ public void onNewWaypointsDone() {
+ getActivity().runOnUiThread(updateChart);
+ }
+
+ @Override
+ public boolean onUnitsChanged(boolean metric) {
+ if (metricUnits == metric) {
+ return false;
+ }
+ metricUnits = metric;
+ chartView.setMetricUnits(metricUnits);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ chartView.requestLayout();
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onReportSpeedChanged(boolean speed) {
+ if (reportSpeed == speed) {
+ return false;
+ }
+ reportSpeed = speed;
+ chartView.setReportSpeed(speed, getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ chartView.requestLayout();
+ }
+ });
+ return true;
+ }
+
+ /**
+ * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void resumeTrackDataHub() {
+ trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
+ trackDataHub.registerTrackDataListener(this, EnumSet.of(
+ ListenerDataType.SELECTED_TRACK_CHANGED,
+ ListenerDataType.TRACK_UPDATES,
+ ListenerDataType.WAYPOINT_UPDATES,
+ ListenerDataType.POINT_UPDATES,
+ ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
+ ListenerDataType.DISPLAY_PREFERENCES));
+ }
+
+ /**
+ * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void pauseTrackDataHub() {
+ trackDataHub.unregisterTrackDataListener(this);
+ trackDataHub = null;
+ }
+
+ /**
+ * Returns true if recording. Needs to be synchronized because trackDataHub
+ * can be accessed by multiple threads.
+ */
+ private synchronized boolean isRecording() {
+ return trackDataHub != null && trackDataHub.isRecordingSelected();
+ }
+
+ /**
+ * Reloads the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void reloadTrackDataHub() {
+ if (trackDataHub != null) {
+ trackDataHub.reloadDataForListener(this);
+ }
+ }
+
+ /**
+ * To zoom in.
+ */
+ private void zoomIn() {
+ chartView.zoomIn();
+ zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
+ zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
+ }
+
+ /**
+ * To zoom out.
+ */
+ private void zoomOut() {
+ chartView.zoomOut();
+ zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
+ zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
+ }
+
+ /**
+ * Given a location, fill in a data point, an array of double[6].
+ * data[0] = time/distance
+ * data[1] = elevation
+ * data[2] = speed
+ * data[3] = power
+ * data[4] = cadence
+ * data[5] = heart rate
+ *
+ * @param location the location
+ * @param data the data point to fill in, can be null
+ */
+ @VisibleForTesting
+ void fillDataPoint(Location location, double data[]) {
+ double timeOrDistance = Double.NaN;
+ double elevation = Double.NaN;
+ double speed = Double.NaN;
+ double power = Double.NaN;
+ double cadence = Double.NaN;
+ double heartRate = Double.NaN;
+
+ // TODO: Use TripStatisticsBuilder
+ if (chartView.getMode() == Mode.BY_DISTANCE) {
+ if (lastLocation != null) {
+ double distance = lastLocation.distanceTo(location) * UnitConversions.M_TO_KM;
+ if (metricUnits) {
+ totalDistance += distance;
+ } else {
+ totalDistance += distance * UnitConversions.KM_TO_MI;
+ }
+ }
+ timeOrDistance = totalDistance;
+ } else {
+ if (startTime == -1L) {
+ startTime = location.getTime();
+ }
+ timeOrDistance = location.getTime() - startTime;
+ }
+
+ elevationBuffer.setNext(metricUnits ? location.getAltitude() : location.getAltitude()
+ * UnitConversions.M_TO_FT);
+ elevation = elevationBuffer.getAverage();
+
+ if (lastLocation == null) {
+ if (Math.abs(location.getSpeed() - INVALID_SPEED) > 1) {
+ speedBuffer.setNext(location.getSpeed());
+ }
+ } else if (TripStatisticsBuilder.isValidSpeed(location.getTime(), location.getSpeed(),
+ lastLocation.getTime(), lastLocation.getSpeed(), speedBuffer)
+ && (location.getSpeed() <= trackMaxSpeed)) {
+ speedBuffer.setNext(location.getSpeed());
+ }
+ speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
+ if (!metricUnits) {
+ speed *= UnitConversions.KM_TO_MI;
+ }
+ if (!reportSpeed) {
+ speed = speed == 0 ? 0.0 : 60.0 / speed;
+ }
+
+ if (location instanceof MyTracksLocation
+ && ((MyTracksLocation) location).getSensorDataSet() != null) {
+ SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
+ if (sensorDataSet.hasPower() && sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
+ && sensorDataSet.getPower().hasValue()) {
+ power = sensorDataSet.getPower().getValue();
+ }
+ if (sensorDataSet.hasCadence()
+ && sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING
+ && sensorDataSet.getCadence().hasValue()) {
+ cadence = sensorDataSet.getCadence().getValue();
+ }
+ if (sensorDataSet.hasHeartRate()
+ && sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
+ && sensorDataSet.getHeartRate().hasValue()) {
+ heartRate = sensorDataSet.getHeartRate().getValue();
+ }
+ }
+
+ if (data != null) {
+ data[0] = timeOrDistance;
+ data[1] = elevation;
+ data[2] = speed;
+ data[3] = power;
+ data[4] = cadence;
+ data[5] = heartRate;
+ }
+ lastLocation = location;
+ }
+
+ @VisibleForTesting
+ ChartView getChartView() {
+ return chartView;
+ }
+
+ @VisibleForTesting
+ void setChartView(ChartView view) {
+ chartView = view;
+ }
+
+ @VisibleForTesting
+ void setTrackMaxSpeed(double value) {
+ trackMaxSpeed = value;
+ }
+
+ @VisibleForTesting
+ void setMetricUnits(boolean value) {
+ metricUnits = value;
+ }
+
+ @VisibleForTesting
+ void setReportSpeed(boolean value) {
+ reportSpeed = value;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
deleted file mode 100644
index 1be7597b7..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * 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 static com.google.android.apps.mytracks.Constants.TAG;
-
-import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
-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.stats.TripStatistics;
-import com.google.android.apps.mytracks.util.GeoRect;
-import com.google.android.apps.mytracks.util.LocationUtils;
-import com.google.android.maps.GeoPoint;
-import com.google.android.maps.MapController;
-import com.google.android.maps.MapView;
-import com.google.android.maps.mytracks.R;
-
-import android.content.Intent;
-import android.location.Location;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.speech.tts.TextToSpeech;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.EnumSet;
-
-/**
- * The map view activity of the MyTracks application.
- *
- * @author Leif Hendrik Wilden
- * @author Rodrigo Damazio
- */
-public class MapActivity extends com.google.android.maps.MapActivity
- implements View.OnTouchListener, View.OnClickListener,
- TrackDataListener {
-
- // Saved instance state keys:
- // ---------------------------
-
- private static final String KEY_CURRENT_LOCATION = "currentLocation";
- private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
-
- private TrackDataHub dataHub;
-
- /**
- * True if the map should be scrolled so that the pointer is always in the
- * visible area.
- */
- private boolean keepMyLocationVisible;
-
- /**
- * True if we've already zoomed into the current location.
- * Only relevant when {@link #keepMyLocationVisible} is true.
- */
- private boolean myLocationWasZoomedIn;
-
- /**
- * The ID of a track on which we want to show a waypoint.
- * The waypoint will be shown as soon as the track is loaded.
- */
- private long showWaypointTrackId;
-
- /**
- * The ID of a waypoint which we want to show.
- * The waypoint will be shown as soon as its track is loaded.
- */
- private long showWaypointId;
-
- /**
- * The track that's currently selected.
- * This differs from {@link TrackDataHub#getSelectedTrackId} in that this one is only set after
- * actual track data has been received.
- */
- private long selectedTrackId;
-
- /**
- * The current pointer location.
- * This is kept to quickly center on it when the user requests.
- */
- private Location currentLocation;
-
- // UI elements:
- // -------------
-
- private RelativeLayout screen;
- private MapView mapView;
- private MapOverlay mapOverlay;
- private LinearLayout messagePane;
- private TextView messageText;
- private LinearLayout busyPane;
-
- /**
- * We are not displaying driving directions. Just an arbitrary track that is
- * not associated to any licensed mapping data. Therefore it should be okay to
- * return false here and still comply with the terms of service.
- */
- @Override
- protected boolean isRouteDisplayed() {
- return false;
- }
-
- /**
- * We are displaying a location. This needs to return true in order to comply
- * with the terms of service.
- */
- @Override
- protected boolean isLocationDisplayed() {
- return true;
- }
-
- // Application life cycle:
- // ------------------------
-
- @Override
- protected void onCreate(Bundle bundle) {
- Log.d(TAG, "MapActivity.onCreate");
- super.onCreate(bundle);
-
- // The volume we want to control is the Text-To-Speech volume
- setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
-
- // Inflate the layout:
- setContentView(R.layout.mytracks_layout);
-
- // Remove the window's background because the MapView will obscure it
- getWindow().setBackgroundDrawable(null);
-
- // Set up a map overlay:
- screen = (RelativeLayout) findViewById(R.id.screen);
- mapView = (MapView) findViewById(R.id.map);
- mapView.requestFocus();
- mapOverlay = new MapOverlay(this);
- mapView.getOverlays().add(mapOverlay);
- mapView.setOnTouchListener(this);
- mapView.setBuiltInZoomControls(true);
- messagePane = (LinearLayout) findViewById(R.id.messagepane);
- messageText = (TextView) findViewById(R.id.messagetext);
- busyPane = (LinearLayout) findViewById(R.id.busypane);
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle bundle) {
- Log.d(TAG, "MapActivity.onRestoreInstanceState");
- if (bundle != null) {
- super.onRestoreInstanceState(bundle);
- keepMyLocationVisible =
- bundle.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
- if (bundle.containsKey(KEY_CURRENT_LOCATION)) {
- currentLocation = (Location) bundle.getParcelable(KEY_CURRENT_LOCATION);
- if (currentLocation != null) {
- showCurrentLocation();
- }
- } else {
- currentLocation = null;
- }
- }
- }
-
- @Override
- protected void onResume() {
- Log.d(TAG, "MapActivity.onResume");
- super.onResume();
-
- dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
- dataHub.registerTrackDataListener(this, EnumSet.of(
- ListenerDataType.SELECTED_TRACK_CHANGED,
- ListenerDataType.POINT_UPDATES,
- ListenerDataType.WAYPOINT_UPDATES,
- ListenerDataType.LOCATION_UPDATES,
- ListenerDataType.COMPASS_UPDATES));
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- Log.d(TAG, "MapActivity.onSaveInstanceState");
- outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
- if (currentLocation != null) {
- outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
- }
- super.onSaveInstanceState(outState);
- }
-
- @Override
- protected void onPause() {
- Log.d(TAG, "MapActivity.onPause");
-
- dataHub.unregisterTrackDataListener(this);
- dataHub = null;
-
- super.onPause();
- }
-
- /**
- * Tests if a location is visible.
- *
- * @param location a given location
- * @return true if the given location is within the visible map area
- */
- private boolean locationIsVisible(Location location) {
- if (location == null || mapView == null) {
- return false;
- }
- GeoPoint center = mapView.getMapCenter();
- int latSpan = mapView.getLatitudeSpan();
- int lonSpan = mapView.getLongitudeSpan();
-
- // Bottom of map view is obscured by zoom controls/buttons.
- // Subtract a margin from the visible area:
- GeoPoint marginBottom = mapView.getProjection().fromPixels(
- 0, mapView.getHeight());
- GeoPoint marginTop = mapView.getProjection().fromPixels(0,
- mapView.getHeight()
- - mapView.getZoomButtonsController().getZoomControls().getHeight());
- int margin =
- Math.abs(marginTop.getLatitudeE6() - marginBottom.getLatitudeE6());
- GeoRect r = new GeoRect(center, latSpan, lonSpan);
- r.top += margin;
-
- GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
- return r.contains(geoPoint);
- }
-
- /**
- * Centers (and keeps centered) the map on the current location.
- */
- public void showMyLocation() {
- dataHub.forceUpdateLocation();
- keepMyLocationVisible = true;
- myLocationWasZoomedIn = false;
- if (currentLocation != null) {
- showCurrentLocation();
- }
- }
-
- /**
- * Moves the location pointer to the current location and center the map if
- * the current location is outside the visible area.
- */
- private void showCurrentLocation() {
- if (mapOverlay == null || mapView == null) {
- return;
- }
-
- mapOverlay.setMyLocation(currentLocation);
- mapView.postInvalidate();
-
- if (currentLocation != null && keepMyLocationVisible && !locationIsVisible(currentLocation)) {
- GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
- MapController controller = mapView.getController();
- controller.animateTo(geoPoint);
- if (!myLocationWasZoomedIn && mapView.getZoomLevel() < 18) {
- // Only zoom in the first time we show the location.
- myLocationWasZoomedIn = true;
- controller.setZoom(18);
- }
- }
- }
-
- @Override
- public void onTrackUpdated(Track track) {
- // We don't care.
- }
-
- /**
- * Zooms and pans the map so that the given track is visible.
- *
- * @param track the track
- */
- private void zoomMapToBoundaries(Track track) {
- if (mapView == null) {
- return;
- }
-
- if (track == null || track.getNumberOfPoints() < 2) {
- return;
- }
-
- TripStatistics stats = track.getStatistics();
- int bottom = stats.getBottom();
- int left = stats.getLeft();
- int latSpanE6 = stats.getTop() - bottom;
- int lonSpanE6 = stats.getRight() - left;
- if (latSpanE6 > 0
- && latSpanE6 < 180E6
- && lonSpanE6 > 0
- && lonSpanE6 < 360E6) {
- keepMyLocationVisible = false;
- GeoPoint center = new GeoPoint(
- bottom + latSpanE6 / 2,
- left + lonSpanE6 / 2);
- if (LocationUtils.isValidGeoPoint(center)) {
- mapView.getController().setCenter(center);
- mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
- }
- }
- }
-
- /**
- * Zooms and pans the map so that the given waypoint is visible.
- */
- public void showWaypoint(long waypointId) {
- MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(this);
- Waypoint wpt = providerUtils.getWaypoint(waypointId);
- if (wpt != null && wpt.getLocation() != null) {
- keepMyLocationVisible = false;
- GeoPoint center = new GeoPoint(
- (int) (wpt.getLocation().getLatitude() * 1E6),
- (int) (wpt.getLocation().getLongitude() * 1E6));
- mapView.getController().setCenter(center);
- mapView.getController().setZoom(20);
- mapView.invalidate();
- }
- }
-
- /**
- * Zooms and pans the map so that the given waypoint is visible, when the given track is loaded.
- * If the track is already loaded, it does that immediately.
- *
- * @param trackId the ID of the track on which to show the waypoint
- * @param waypointId the ID of the waypoint to show
- */
- public void showWaypoint(long trackId, long waypointId) {
- synchronized (this) {
- if (trackId == selectedTrackId) {
- showWaypoint(waypointId);
- return;
- }
-
- showWaypointTrackId = trackId;
- showWaypointId = waypointId;
- }
- }
-
- /**
- * Does the proper zooming/panning for a just-loaded track.
- * This may be either zooming to a waypoint that has been previously selected, or
- * zooming to the whole track.
- *
- * @param track the loaded track
- */
- private void zoomLoadedTrack(Track track) {
- synchronized (this) {
- if (track.getId() == showWaypointTrackId) {
- // There's a waypoint to show in this track.
- showWaypoint(showWaypointId);
-
- showWaypointId = 0L;
- showWaypointTrackId = 0L;
- } else {
- // Zoom out to show the whole track.
- zoomMapToBoundaries(track);
- }
- }
- }
-
- @Override
- public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- boolean trackSelected = track != null;
- mapOverlay.setTrackDrawingEnabled(trackSelected);
-
- if (trackSelected) {
- busyPane.setVisibility(View.VISIBLE);
-
- synchronized (this) {
- // Need to get the track ID only at this point, to prevent a race condition
- // among showWaypoint, zoomLoadedTrack and dataHub.loadTrack.
- selectedTrackId = track.getId();
- zoomLoadedTrack(track);
- }
-
- mapOverlay.setShowEndMarker(!isRecording);
- busyPane.setVisibility(View.GONE);
- }
- mapView.invalidate();
- }
- });
- }
-
- /**
- * Returns whether the map is currently in satellite view mode.
- */
- public boolean isSatelliteView() {
- return mapView.isSatellite();
- }
-
- /**
- * Changes whether the map should be in satellite view mode.
- */
- public void setSatelliteView(boolean sat) {
- mapView.setSatellite(sat);
- }
-
- @Override
- public void onClick(View v) {
- if (v == messagePane) {
- startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
- }
- }
-
- /**
- * We want the pointer to become visible again in case of the next location
- * update:
- */
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
- if (!locationIsVisible(currentLocation)) {
- keepMyLocationVisible = false;
- }
- }
- return false;
- }
-
- @Override
- public void onProviderStateChange(ProviderState state) {
- final int messageId;
- final boolean isGpsDisabled;
- switch (state) {
- case DISABLED:
- messageId = R.string.gps_need_to_enable;
- isGpsDisabled = true;
- break;
- case NO_FIX:
- case BAD_FIX:
- messageId = R.string.gps_wait_for_fix;
- isGpsDisabled = false;
- break;
- case GOOD_FIX:
- // Nothing to show.
- messageId = -1;
- isGpsDisabled = false;
- break;
- default:
- throw new IllegalArgumentException("Unexpected state: " + state);
- }
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (messageId != -1) {
- messageText.setText(messageId);
- messagePane.setVisibility(View.VISIBLE);
-
- if (isGpsDisabled) {
- // Give a warning about this state.
- Toast.makeText(MapActivity.this,
- R.string.gps_not_found,
- Toast.LENGTH_LONG).show();
-
- // Make clicking take the user to the location settings.
- messagePane.setOnClickListener(MapActivity.this);
- } else {
- messagePane.setOnClickListener(null);
- }
- } else {
- messagePane.setVisibility(View.GONE);
- }
-
- screen.requestLayout();
- }
- });
- }
-
- @Override
- public void onCurrentLocationChanged(Location location) {
- currentLocation = location;
- showCurrentLocation();
- }
-
- @Override
- public void onCurrentHeadingChanged(double heading) {
- synchronized (this) {
- if (mapOverlay.setHeading((float) heading)) {
- mapView.postInvalidate();
- }
- }
- }
-
- @Override
- public void clearWaypoints() {
- mapOverlay.clearWaypoints();
- }
-
- @Override
- public void onNewWaypoint(Waypoint waypoint) {
- if (LocationUtils.isValidLocation(waypoint.getLocation())) {
- // TODO: Optimize locking inside addWaypoint
- mapOverlay.addWaypoint(waypoint);
- }
- }
-
- @Override
- public void onNewWaypointsDone() {
- mapView.postInvalidate();
- }
-
- @Override
- public void clearTrackPoints() {
- mapOverlay.clearPoints();
- }
-
- @Override
- public void onNewTrackPoint(Location loc) {
- mapOverlay.addLocation(loc);
- }
-
- @Override
- public void onSegmentSplit() {
- mapOverlay.addSegmentSplit();
- }
-
- @Override
- public void onSampledOutTrackPoint(Location loc) {
- // We don't care.
- }
-
- @Override
- public void onNewTrackPointsDone() {
- mapView.postInvalidate();
- }
-
- @Override
- public boolean onUnitsChanged(boolean metric) {
- // We don't care.
- return false;
- }
-
- @Override
- public boolean onReportSpeedChanged(boolean reportSpeed) {
- // We don't care.
- return false;
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java b/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java
new file mode 100644
index 000000000..f8e5abf77
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/MapFragment.java
@@ -0,0 +1,520 @@
+/*
+ * 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.MyTracksProviderUtils;
+import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
+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.stats.TripStatistics;
+import com.google.android.apps.mytracks.util.GeoRect;
+import com.google.android.apps.mytracks.util.LocationUtils;
+import com.google.android.maps.GeoPoint;
+import com.google.android.maps.MapController;
+import com.google.android.maps.MapView;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Intent;
+import android.location.Location;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.EnumSet;
+
+/**
+ * A fragment to display map to the user.
+ *
+ * @author Leif Hendrik Wilden
+ * @author Rodrigo Damazio
+ */
+public class MapFragment extends Fragment
+ implements View.OnTouchListener, View.OnClickListener, TrackDataListener {
+
+ private static final String KEY_CURRENT_LOCATION = "currentLocation";
+ private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
+
+ private TrackDataHub trackDataHub;
+
+ // True to keep my location visible.
+ private boolean keepMyLocationVisible;
+
+ // True to zoom to my location. Only apply when keepMyLocationVisible is true.
+ private boolean zoomToMyLocation;
+
+ // The track id of the waypoint to show.
+ private long waypointTrackId;
+
+ // The waypoint id to show
+ private long waypointId;
+
+ // The current selected track id. Set in onSelectedTrackChanged.
+ private long currentSelectedTrackId;
+
+ // The current location. Set in onCurrentLocationChanged.
+ private Location currentLocation;
+
+ // UI elements
+ private View mapViewContainer;
+ private MapOverlay mapOverlay;
+ private RelativeLayout screen;
+ private MapView mapView;
+ private LinearLayout messagePane;
+ private TextView messageText;
+ private LinearLayout busyPane;
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
+
+ mapOverlay = new MapOverlay(getActivity());
+
+ screen = (RelativeLayout) mapViewContainer.findViewById(R.id.screen);
+ mapView = (MapView) mapViewContainer.findViewById(R.id.map);
+ mapView.requestFocus();
+ mapView.setOnTouchListener(this);
+ mapView.setBuiltInZoomControls(true);
+ mapView.getOverlays().add(mapOverlay);
+ messagePane = (LinearLayout) mapViewContainer.findViewById(R.id.messagepane);
+ messageText = (TextView) mapViewContainer.findViewById(R.id.messagetext);
+ busyPane = (LinearLayout) mapViewContainer.findViewById(R.id.busypane);
+
+ return mapViewContainer;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (savedInstanceState != null) {
+ keepMyLocationVisible = savedInstanceState.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
+ currentLocation = (Location) savedInstanceState.getParcelable(KEY_CURRENT_LOCATION);
+ if (currentLocation != null) {
+ updateCurrentLocation();
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ resumeTrackDataHub();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
+ if (currentLocation != null) {
+ outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseTrackDataHub();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent();
+ if (parentViewGroup != null) {
+ parentViewGroup.removeView(mapViewContainer);
+ }
+ }
+
+ /**
+ * Shows my location.
+ */
+ public void showMyLocation() {
+ updateTrackDataHub();
+ keepMyLocationVisible = true;
+ zoomToMyLocation = true;
+ if (currentLocation != null) {
+ updateCurrentLocation();
+ }
+ }
+
+ /**
+ * Shows the waypoint.
+ *
+ * @param id the waypoint id
+ */
+ public void showWaypoint(long id) {
+ MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
+ Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
+ if (waypoint != null && waypoint.getLocation() != null) {
+ keepMyLocationVisible = false;
+ GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6),
+ (int) (waypoint.getLocation().getLongitude() * 1E6));
+ mapView.getController().setCenter(center);
+ mapView.getController().setZoom(mapView.getMaxZoomLevel());
+ mapView.invalidate();
+ }
+ }
+
+ /**
+ * Shows the waypoint.
+ *
+ * @param trackId the track id
+ * @param id the waypoint id
+ */
+ public void showWaypoint(long trackId, long id) {
+ /*
+ * Synchronize to prevent race condition in changing waypointTrackId and
+ * waypointId variables.
+ */
+ synchronized (this) {
+ if (trackId == currentSelectedTrackId) {
+ showWaypoint(id);
+ waypointTrackId = -1L;
+ waypointId = -1L;
+ return;
+ }
+ waypointTrackId = trackId;
+ waypointId = id;
+ }
+ }
+
+ /**
+ * Returns true if in satellite mode.
+ */
+ public boolean isSatelliteView() {
+ return mapView.isSatellite();
+ }
+
+ /**
+ * Sets the satellite mode
+ *
+ * @param enabled true for satellite mode, false for map mode
+ */
+ public void setSatelliteView(boolean enabled) {
+ mapView.setSatellite(enabled);
+ }
+
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
+ if (!isVisible(currentLocation)) {
+ /*
+ * Only set to false when no longer visible. Thus can keep showing the
+ * current location with the next location update.
+ */
+ keepMyLocationVisible = false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == messagePane) {
+ startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+ }
+ }
+
+ @Override
+ public void onProviderStateChange(ProviderState state) {
+ final int messageId;
+ final boolean isGpsDisabled;
+ switch (state) {
+ case DISABLED:
+ messageId = R.string.gps_need_to_enable;
+ isGpsDisabled = true;
+ break;
+ case NO_FIX:
+ case BAD_FIX:
+ messageId = R.string.gps_wait_for_fix;
+ isGpsDisabled = false;
+ break;
+ case GOOD_FIX:
+ messageId = -1;
+ isGpsDisabled = false;
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected state: " + state);
+ }
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (messageId != -1) {
+ messageText.setText(messageId);
+ messagePane.setVisibility(View.VISIBLE);
+
+ if (isGpsDisabled) {
+ Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();
+
+ // Click to show the location source settings
+ messagePane.setOnClickListener(MapFragment.this);
+ } else {
+ messagePane.setOnClickListener(null);
+ }
+ } else {
+ messagePane.setVisibility(View.GONE);
+ }
+ screen.requestLayout();
+ }
+ });
+ }
+
+ @Override
+ public void onCurrentLocationChanged(Location location) {
+ currentLocation = location;
+ updateCurrentLocation();
+ }
+
+ @Override
+ public void onCurrentHeadingChanged(double heading) {
+ if (mapOverlay.setHeading((float) heading)) {
+ mapView.postInvalidate();
+ }
+ }
+
+ @Override
+ public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean hasTrack = track != null;
+ mapOverlay.setTrackDrawingEnabled(hasTrack);
+
+ if (hasTrack) {
+ busyPane.setVisibility(View.VISIBLE);
+
+ synchronized (this) {
+ /*
+ * Synchronize to prevent race condition in changing waypointTrackId
+ * and waypointId variables.
+ */
+ currentSelectedTrackId = track.getId();
+ updateMap(track);
+ }
+ mapOverlay.setShowEndMarker(!isRecording);
+ busyPane.setVisibility(View.GONE);
+ }
+ mapView.invalidate();
+ }
+ });
+ }
+
+ @Override
+ public void onTrackUpdated(Track track) {
+ // We don't care.
+ }
+
+ @Override
+ public void clearTrackPoints() {
+ mapOverlay.clearPoints();
+ }
+
+ @Override
+ public void onNewTrackPoint(Location location) {
+ if (LocationUtils.isValidLocation(location)) {
+ mapOverlay.addLocation(location);
+ }
+ }
+
+ @Override
+ public void onSampledOutTrackPoint(Location loc) {
+ // We don't care.
+ }
+
+ @Override
+ public void onSegmentSplit() {
+ mapOverlay.addSegmentSplit();
+ }
+
+ @Override
+ public void onNewTrackPointsDone() {
+ mapView.postInvalidate();
+ }
+
+ @Override
+ public void clearWaypoints() {
+ mapOverlay.clearWaypoints();
+ }
+
+ @Override
+ public void onNewWaypoint(Waypoint waypoint) {
+ if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
+ // TODO: Optimize locking inside addWaypoint
+ mapOverlay.addWaypoint(waypoint);
+ }
+ }
+
+ @Override
+ public void onNewWaypointsDone() {
+ mapView.postInvalidate();
+ }
+
+ @Override
+ public boolean onUnitsChanged(boolean metric) {
+ // We don't care.
+ return false;
+ }
+
+ @Override
+ public boolean onReportSpeedChanged(boolean reportSpeed) {
+ // We don't care.
+ return false;
+ }
+
+ /**
+ * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void resumeTrackDataHub() {
+ trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
+ trackDataHub.registerTrackDataListener(this, EnumSet.of(
+ ListenerDataType.SELECTED_TRACK_CHANGED,
+ ListenerDataType.WAYPOINT_UPDATES,
+ ListenerDataType.POINT_UPDATES,
+ ListenerDataType.LOCATION_UPDATES,
+ ListenerDataType.COMPASS_UPDATES));
+ }
+
+ /**
+ * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void pauseTrackDataHub() {
+ trackDataHub.unregisterTrackDataListener(this);
+ trackDataHub = null;
+ }
+
+ /**
+ * Updates the trackDataHub. Needs to be synchronized because trackDataHub can be
+ * accessed by multiple threads.
+ */
+ private synchronized void updateTrackDataHub() {
+ if (trackDataHub != null) {
+ trackDataHub.forceUpdateLocation();
+ }
+ }
+
+ /**
+ * Updates the map by either zooming to the requested waypoint or showing the track.
+ *
+ * @param track the track
+ */
+ private void updateMap(Track track) {
+ if (track.getId() == waypointTrackId) {
+ // Show the waypoint
+ showWaypoint(waypointId);
+
+ waypointTrackId = -1L;
+ waypointId = -1L;
+ } else {
+ // Show the track
+ showTrack(track);
+ }
+ }
+
+ /**
+ * Returns true if the location is visible.
+ *
+ * @param location the location
+ */
+ private boolean isVisible(Location location) {
+ if (location == null || mapView == null) {
+ return false;
+ }
+ GeoPoint mapCenter = mapView.getMapCenter();
+ int latitudeSpan = mapView.getLatitudeSpan();
+ int longitudeSpan = mapView.getLongitudeSpan();
+
+ /*
+ * The bottom of the mapView is obscured by the zoom controls, subtract its
+ * height from the visible area.
+ */
+ GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
+ GeoPoint zoomControlTop = mapView.getProjection().fromPixels(
+ 0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
+ int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6()
+ - zoomControlBottom.getLatitudeE6());
+ GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
+ geoRect.top += zoomControlMargin;
+
+ GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
+ return geoRect.contains(geoPoint);
+ }
+
+ /**
+ * Updates the current location and centers it if necessary.
+ */
+ private void updateCurrentLocation() {
+ if (mapOverlay == null || mapView == null) {
+ return;
+ }
+
+ mapOverlay.setMyLocation(currentLocation);
+ mapView.postInvalidate();
+
+ if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) {
+ GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
+ MapController mapController = mapView.getController();
+ mapController.animateTo(geoPoint);
+ if (zoomToMyLocation) {
+ // Only zoom in the first time we show the location.
+ zoomToMyLocation = false;
+ if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) {
+ mapController.setZoom(mapView.getMaxZoomLevel());
+ }
+ }
+ }
+ }
+
+ /**
+ * Shows the track.
+ *
+ * @param track the track
+ */
+ private void showTrack(Track track) {
+ if (mapView == null || track == null || track.getNumberOfPoints() < 2) {
+ return;
+ }
+
+ TripStatistics tripStatistics = track.getStatistics();
+ int bottom = tripStatistics.getBottom();
+ int left = tripStatistics.getLeft();
+ int latitudeSpanE6 = tripStatistics.getTop() - bottom;
+ int longitudeSpanE6 = tripStatistics.getRight() - left;
+ if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0
+ && longitudeSpanE6 < 360E6) {
+ keepMyLocationVisible = false;
+ GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2);
+ if (LocationUtils.isValidGeoPoint(center)) {
+ mapView.getController().setCenter(center);
+ mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6);
+ }
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java b/MyTracks/src/com/google/android/apps/mytracks/NavControls.java
deleted file mode 100644
index 655a5fab8..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/NavControls.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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 android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
-
-/**
- * Creates previous and next arrows for a given activity.
- *
- * @author Leif Hendrik Wilden
- */
-public class NavControls {
-
- private static final int KEEP_VISIBLE_MILLIS = 4000;
- private static final boolean FADE_CONTROLS = true;
-
- private static final Animation SHOW_NEXT_ANIMATION =
- new AlphaAnimation(0F, 1F);
- private static final Animation HIDE_NEXT_ANIMATION =
- new AlphaAnimation(1F, 0F);
- private static final Animation SHOW_PREV_ANIMATION =
- new AlphaAnimation(0F, 1F);
- private static final Animation HIDE_PREV_ANIMATION =
- new AlphaAnimation(1F, 0F);
-
- /**
- * A touchable image view.
- * When touched it changes the navigation control icons accordingly.
- */
- private class TouchLayout extends RelativeLayout implements Runnable {
- private final boolean isLeft;
- private final ImageView icon;
-
- public TouchLayout(Context context, boolean isLeft) {
- super(context);
- this.isLeft = isLeft;
- this.icon = new ImageView(context);
- icon.setVisibility(View.GONE);
- addView(icon);
- }
-
- public void setIcon(Drawable drawable) {
- icon.setImageDrawable(drawable);
- icon.setVisibility(View.VISIBLE);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- setPressed(true);
- shiftIcons(isLeft);
-
- // Call the user back
- handler.post(this);
-
- break;
- case MotionEvent.ACTION_UP:
- setPressed(false);
- break;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public void run() {
- touchRunnable.run();
- setPressed(false);
- }
- }
-
- private final Handler handler = new Handler();
- private final Runnable dismissControls = new Runnable() {
- public void run() {
- hide();
- }
- };
-
- private final TouchLayout prevImage;
- private final TouchLayout nextImage;
- private final TypedArray leftIcons;
- private final TypedArray rightIcons;
- private final Runnable touchRunnable;
-
- private boolean isVisible = false;
- private int currentIcons;
-
- public NavControls(Context context, ViewGroup container,
- TypedArray leftIcons, TypedArray rightIcons,
- Runnable touchRunnable) {
- this.leftIcons = leftIcons;
- this.rightIcons = rightIcons;
- this.touchRunnable = touchRunnable;
-
- if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
- throw new IllegalArgumentException("Invalid icons specified");
- }
- if (touchRunnable == null) {
- throw new NullPointerException("Runnable cannot be null");
- }
-
- LayoutParams prevParams = new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
- LayoutParams nextParams = new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT);
-
- prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
- nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
- nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
-
- nextImage = new TouchLayout(context, false);
- prevImage = new TouchLayout(context, true);
- nextImage.setLayoutParams(nextParams);
- prevImage.setLayoutParams(prevParams);
- nextImage.setVisibility(View.INVISIBLE);
- prevImage.setVisibility(View.INVISIBLE);
-
- container.addView(prevImage);
- container.addView(nextImage);
-
- prevImage.setIcon(leftIcons.getDrawable(0));
- nextImage.setIcon(rightIcons.getDrawable(0));
- this.currentIcons = 0;
- }
-
- private void keepVisible() {
- if (isVisible && FADE_CONTROLS) {
- handler.removeCallbacks(dismissControls);
- handler.postDelayed(dismissControls, KEEP_VISIBLE_MILLIS);
- }
- }
-
- public void show() {
- if (!isVisible) {
- SHOW_PREV_ANIMATION.setDuration(500);
- SHOW_PREV_ANIMATION.startNow();
- prevImage.setPressed(false);
- prevImage.setAnimation(SHOW_PREV_ANIMATION);
- prevImage.setVisibility(View.VISIBLE);
-
- SHOW_NEXT_ANIMATION.setDuration(500);
- SHOW_NEXT_ANIMATION.startNow();
- nextImage.setPressed(false);
- nextImage.setAnimation(SHOW_NEXT_ANIMATION);
- nextImage.setVisibility(View.VISIBLE);
-
- isVisible = true;
- keepVisible();
- } else {
- keepVisible();
- }
- }
-
- public void hideNow() {
- handler.removeCallbacks(dismissControls);
- isVisible = false;
- prevImage.clearAnimation();
- prevImage.setVisibility(View.INVISIBLE);
- nextImage.clearAnimation();
- nextImage.setVisibility(View.INVISIBLE);
- }
-
- public void hide() {
- isVisible = false;
- prevImage.setAnimation(HIDE_PREV_ANIMATION);
- HIDE_PREV_ANIMATION.setDuration(500);
- HIDE_PREV_ANIMATION.startNow();
- prevImage.setVisibility(View.INVISIBLE);
-
- nextImage.setAnimation(HIDE_NEXT_ANIMATION);
- HIDE_NEXT_ANIMATION.setDuration(500);
- HIDE_NEXT_ANIMATION.startNow();
- nextImage.setVisibility(View.INVISIBLE);
- }
-
- public int getCurrentIcons() {
- return currentIcons;
- }
-
- private void shiftIcons(boolean isLeft) {
- // Increment or decrement by one, with wrap around
- currentIcons = (currentIcons + leftIcons.length() +
- (isLeft ? -1 : 1)) % leftIcons.length();
- prevImage.setIcon(leftIcons.getDrawable(currentIcons));
- nextImage.setIcon(rightIcons.getDrawable(currentIcons));
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
deleted file mode 100644
index ad7a046f6..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsActivity.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * 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 static com.google.android.apps.mytracks.Constants.TAG;
-
-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.ServiceUtils;
-import com.google.android.apps.mytracks.util.UnitConversions;
-import com.google.android.maps.mytracks.R;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.location.Location;
-import android.os.Bundle;
-import android.speech.tts.TextToSpeech;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.util.EnumSet;
-
-/**
- * An activity that displays track statistics to the user.
- *
- * @author Sandor Dornbush
- * @author Rodrigo Damazio
- */
-public class StatsActivity extends Activity implements TrackDataListener {
- /**
- * A runnable for posting to the UI thread. Will update the total time field.
- */
- private final Runnable updateResults = new Runnable() {
- public void run() {
- if (dataHub != null && dataHub.isRecordingSelected()) {
- utils.setTime(R.id.total_time_register,
- System.currentTimeMillis() - startTime);
- }
- }
- };
-
- private StatsUtilities utils;
- private UIUpdateThread thread;
-
- /**
- * The start time of the selected track.
- */
- private long startTime = -1;
-
- private TrackDataHub dataHub;
- private SharedPreferences preferences;
-
- /**
- * A thread that updates the total time field every second.
- */
- private class UIUpdateThread extends Thread {
-
- public UIUpdateThread() {
- super();
- Log.i(TAG, "Created UI update thread");
- }
-
- @Override
- public void run() {
- Log.i(TAG, "Started UI update thread");
- while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
- runOnUiThread(updateResults);
- try {
- Thread.sleep(1000L);
- } catch (InterruptedException e) {
- Log.w(TAG, "StatsActivity: Caught exception on sleep.", e);
- break;
- }
- }
- Log.w(TAG, "UIUpdateThread finished.");
- }
- }
-
- /** Called when the activity is first created. */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
- utils = new StatsUtilities(this);
-
- // The volume we want to control is the Text-To-Speech volume
- setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
-
- setContentView(R.layout.stats);
-
- ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
- sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
-
- showUnknownLocation();
- updateLabels();
-
- DisplayMetrics metrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(metrics);
- if (metrics.heightPixels > 600) {
- ((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f);
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
- dataHub.registerTrackDataListener(this, EnumSet.of(
- ListenerDataType.SELECTED_TRACK_CHANGED,
- ListenerDataType.TRACK_UPDATES,
- ListenerDataType.LOCATION_UPDATES,
- ListenerDataType.DISPLAY_PREFERENCES));
- }
-
- @Override
- protected void onPause() {
- dataHub.unregisterTrackDataListener(this);
- dataHub = null;
-
- if (thread != null) {
- thread.interrupt();
- thread = null;
- }
-
- super.onStop();
- }
-
- @Override
- public boolean onUnitsChanged(boolean metric) {
- if (utils.isMetricUnits() == metric) {
- return false;
- }
- utils.setMetricUnits(metric);
- updateLabels();
- return true;
- }
-
- @Override
- public boolean onReportSpeedChanged(boolean speed) {
- if (utils.isReportSpeed() == speed) {
- return false;
- }
- utils.setReportSpeed(speed);
- updateLabels();
- return true;
- }
-
- private void updateLabels() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- utils.updateUnits();
- utils.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
- utils.setSpeedLabels();
- }
- });
- }
-
- /**
- * Updates the given location fields (latitude, longitude, altitude) and all
- * other fields.
- *
- * @param l may be null (will set location fields to unknown)
- */
- private void showLocation(Location l) {
- utils.setAltitude(R.id.elevation_register, l.getAltitude());
- utils.setLatLong(R.id.latitude_register, l.getLatitude());
- utils.setLatLong(R.id.longitude_register, l.getLongitude());
- utils.setSpeed(R.id.speed_register, l.getSpeed() * UnitConversions.MS_TO_KMH);
- }
-
- private void showUnknownLocation() {
- utils.setUnknown(R.id.elevation_register);
- utils.setUnknown(R.id.latitude_register);
- utils.setUnknown(R.id.longitude_register);
- utils.setUnknown(R.id.speed_register);
- }
-
- @Override
- public void onSelectedTrackChanged(Track track, boolean isRecording) {
- /*
- * Checks if this activity needs to update live track data or not.
- * If so, make sure that:
- * a) a thread keeps updating the total time
- * b) a location listener is registered
- * c) a content observer is registered
- * Otherwise unregister listeners, observers, and kill update thread.
- */
- final boolean startThread = (thread == null) && isRecording;
- final boolean killThread = (thread != null) && (!isRecording);
- if (startThread) {
- thread = new UIUpdateThread();
- thread.start();
- } else if (killThread) {
- thread.interrupt();
- thread = null;
- }
- }
-
- @Override
- public void onCurrentLocationChanged(final Location loc) {
- TrackDataHub localDataHub = dataHub;
- if (localDataHub != null && localDataHub.isRecordingSelected()) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (loc != null) {
- showLocation(loc);
- } else {
- showUnknownLocation();
- }
- }
- });
- }
- }
-
- @Override
- public void onCurrentHeadingChanged(double heading) {
- // We don't care.
- }
-
- @Override
- public void onProviderStateChange(ProviderState state) {
- switch (state) {
- case DISABLED:
- case NO_FIX:
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- showUnknownLocation();
- }
- });
- break;
- }
- }
-
- @Override
- public void onTrackUpdated(final Track track) {
- TrackDataHub localDataHub = dataHub;
- final boolean recordingSelected = localDataHub != null && localDataHub.isRecordingSelected();
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (track == null || track.getStatistics() == null) {
- utils.setAllToUnknown();
- return;
- }
-
- startTime = track.getStatistics().getStartTime();
- if (!recordingSelected) {
- utils.setTime(R.id.total_time_register,
- track.getStatistics().getTotalTime());
- showUnknownLocation();
- }
- utils.setAllStats(track.getStatistics());
- }
- });
- }
-
- @Override
- public void clearWaypoints() {
- // We don't care.
- }
-
- @Override
- public void onNewWaypoint(Waypoint wpt) {
- // We don't care.
- }
-
- @Override
- public void onNewWaypointsDone() {
- // We don't care.
- }
-
- @Override
- public void clearTrackPoints() {
- // We don't care.
- }
-
- @Override
- public void onNewTrackPoint(Location loc) {
- // We don't care.
- }
-
- @Override
- public void onSegmentSplit() {
- // We don't care.
- }
-
- @Override
- public void onSampledOutTrackPoint(Location loc) {
- // We don't care.
- }
-
- @Override
- public void onNewTrackPointsDone() {
- // We don't care.
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java b/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java
new file mode 100644
index 000000000..5cb3b3627
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsFragment.java
@@ -0,0 +1,322 @@
+/*
+ * 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.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.ServiceUtils;
+import com.google.android.apps.mytracks.util.UnitConversions;
+import com.google.android.maps.mytracks.R;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.location.Location;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ScrollView;
+
+import java.util.EnumSet;
+
+/**
+ * A fragment to display track statistics to the user.
+ *
+ * @author Sandor Dornbush
+ * @author Rodrigo Damazio
+ */
+public class StatsFragment extends Fragment implements TrackDataListener {
+
+ private static final String TAG = StatsFragment.class.getSimpleName();
+
+ private SharedPreferences sharedPreferences;
+ private StatsUtilities statsUtilities;
+ private TrackDataHub trackDataHub;
+ private UiUpdateThread uiUpdateThread;
+
+ // The start time of the current track.
+ private long startTime = -1L;
+
+ // A runnable to update the total time field.
+ private final Runnable updateTotalTime = new Runnable() {
+ public void run() {
+ if (isRecording()) {
+ statsUtilities.setTime(R.id.total_time_register, System.currentTimeMillis() - startTime);
+ }
+ }
+ };
+
+ /**
+ * A thread that updates the total time field every second.
+ */
+ private class UiUpdateThread extends Thread {
+
+ @Override
+ public void run() {
+ Log.d(TAG, "UI update thread started");
+ while (ServiceUtils.isRecording(getActivity(), null, sharedPreferences)) {
+ getActivity().runOnUiThread(updateTotalTime);
+ try {
+ Thread.sleep(1000L);
+ } catch (InterruptedException e) {
+ Log.d(TAG, "UI update thread caught exception", e);
+ break;
+ }
+ }
+ Log.d(TAG, "UI update thread finished");
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sharedPreferences = getActivity()
+ .getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
+ statsUtilities = new StatsUtilities(getActivity());
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.stats, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ ScrollView scrollView = (ScrollView) getActivity().findViewById(R.id.scrolly);
+ scrollView.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
+ updateLabels();
+ setLocationUnknown();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ resumeTrackDataHub();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ pauseTrackDataHub();
+ if (uiUpdateThread != null) {
+ uiUpdateThread.interrupt();
+ uiUpdateThread = null;
+ }
+ }
+
+ @Override
+ public void onProviderStateChange(ProviderState state) {
+ if (state == ProviderState.DISABLED || state == ProviderState.NO_FIX) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ setLocationUnknown();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onCurrentLocationChanged(final Location location) {
+ if (isRecording()) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (location != null) {
+ setLocation(location);
+ } else {
+ setLocationUnknown();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onCurrentHeadingChanged(double heading) {
+ // We don't care.
+ }
+
+ @Override
+ public void onSelectedTrackChanged(Track track, boolean isRecording) {
+ if (uiUpdateThread == null && isRecording) {
+ uiUpdateThread = new UiUpdateThread();
+ uiUpdateThread.start();
+ } else if (uiUpdateThread != null && !isRecording) {
+ uiUpdateThread.interrupt();
+ uiUpdateThread = null;
+ }
+ }
+
+ @Override
+ public void onTrackUpdated(final Track track) {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (track == null || track.getStatistics() == null) {
+ statsUtilities.setAllToUnknown();
+ return;
+ }
+
+ startTime = track.getStatistics().getStartTime();
+ if (!isRecording()) {
+ statsUtilities.setTime(R.id.total_time_register, track.getStatistics().getTotalTime());
+ setLocationUnknown();
+ }
+ statsUtilities.setAllStats(track.getStatistics());
+ }
+ });
+ }
+
+ @Override
+ public void clearTrackPoints() {
+ // We don't care.
+ }
+
+ @Override
+ public void onNewTrackPoint(Location loc) {
+ // We don't care.
+ }
+
+ @Override
+ public void onSampledOutTrackPoint(Location loc) {
+ // We don't care.
+ }
+
+ @Override
+ public void onSegmentSplit() {
+ // We don't care.
+ }
+
+ @Override
+ public void onNewTrackPointsDone() {
+ // We don't care.
+ }
+
+ @Override
+ public void clearWaypoints() {
+ // We don't care.
+ }
+
+ @Override
+ public void onNewWaypoint(Waypoint wpt) {
+ // We don't care.
+ }
+
+ @Override
+ public void onNewWaypointsDone() {
+ // We don't care.
+ }
+
+ @Override
+ public boolean onUnitsChanged(boolean metric) {
+ if (statsUtilities.isMetricUnits() == metric) {
+ return false;
+ }
+ statsUtilities.setMetricUnits(metric);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ updateLabels();
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public boolean onReportSpeedChanged(boolean speed) {
+ if (statsUtilities.isReportSpeed() == speed) {
+ return false;
+ }
+ statsUtilities.setReportSpeed(speed);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ updateLabels();
+ }
+ });
+ return true;
+ }
+
+ /**
+ * Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
+ * be accessed by multiple threads.
+ */
+ private synchronized void resumeTrackDataHub() {
+ trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
+ trackDataHub.registerTrackDataListener(this, EnumSet.of(
+ ListenerDataType.SELECTED_TRACK_CHANGED,
+ ListenerDataType.TRACK_UPDATES,
+ ListenerDataType.LOCATION_UPDATES,
+ ListenerDataType.DISPLAY_PREFERENCES));
+ }
+
+ /**
+ * Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
+ * be accessed by multiple threads.
+ */
+ private synchronized void pauseTrackDataHub() {
+ trackDataHub.unregisterTrackDataListener(this);
+ trackDataHub = null;
+ }
+
+ /**
+ * Returns true if recording. Needs to be synchronized because trackDataHub
+ * can be accessed by multiple threads.
+ */
+ private synchronized boolean isRecording() {
+ return trackDataHub != null && trackDataHub.isRecordingSelected();
+ }
+
+ /**
+ * Updates the labels.
+ */
+ private void updateLabels() {
+ statsUtilities.updateUnits();
+ statsUtilities.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
+ statsUtilities.setSpeedLabels();
+ }
+
+ /**
+ * Sets the current location.
+ *
+ * @param location the current location
+ */
+ private void setLocation(Location location) {
+ statsUtilities.setAltitude(R.id.elevation_register, location.getAltitude());
+ statsUtilities.setLatLong(R.id.latitude_register, location.getLatitude());
+ statsUtilities.setLatLong(R.id.longitude_register, location.getLongitude());
+ statsUtilities.setSpeed(R.id.speed_register, location.getSpeed() * UnitConversions.MS_TO_KMH);
+ }
+
+ /**
+ * Sets the current location to unknown.
+ */
+ private void setLocationUnknown() {
+ statsUtilities.setUnknown(R.id.elevation_register);
+ statsUtilities.setUnknown(R.id.latitude_register);
+ statsUtilities.setUnknown(R.id.longitude_register);
+ statsUtilities.setUnknown(R.id.speed_register);
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java b/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java
index 96ee5a23f..b8a6fef65 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/StatsUtilities.java
@@ -262,6 +262,6 @@ public class StatsUtilities {
R.string.stat_average_moving_pace);
setSpeedLabel(R.id.max_speed_label,
R.string.stat_max_speed,
- R.string.stat_min_pace);
+ R.string.stat_fastest_pace);
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TabManager.java b/MyTracks/src/com/google/android/apps/mytracks/TabManager.java
new file mode 100644
index 000000000..5159eacb1
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/TabManager.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 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.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.view.View;
+import android.widget.TabHost;
+
+import java.util.HashMap;
+
+/**
+ * This is a helper class that implements a generic mechanism for associating
+ * fragments with the tabs in a tab host. It relies on a trick. Normally a tab
+ * host has a simple API for supplying a View or Intent that each tab will show.
+ * This is not sufficient for switching between fragments. So instead we make
+ * the content part of the tab host 0dp high (it is not shown) and the
+ * TabManager supplies its own dummy view to show as the tab content. It listens
+ * to changes in tabs, and takes care of switch to the correct fragment shown in
+ * a separate content area whenever the selected tab changes.
+ *
+ * Copied from the Fragment Tabs example in the API 4+ Support Demos.
+ *
+ * @author Jimmy Shih
+ */
+public class TabManager implements TabHost.OnTabChangeListener {
+ private final FragmentActivity mActivity;
+ private final TabHost mTabHost;
+ private final int mContainerId;
+ private final HashMap mTabs = new HashMap();
+ TabInfo mLastTab;
+
+ static final class TabInfo {
+ private final String tag;
+ private final Class> clss;
+ private final Bundle args;
+ private Fragment fragment;
+
+ TabInfo(String _tag, Class> _class, Bundle _args) {
+ tag = _tag;
+ clss = _class;
+ args = _args;
+ }
+ }
+
+ static class DummyTabFactory implements TabHost.TabContentFactory {
+ private final Context mContext;
+
+ public DummyTabFactory(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public View createTabContent(String tag) {
+ View v = new View(mContext);
+ v.setMinimumWidth(0);
+ v.setMinimumHeight(0);
+ return v;
+ }
+ }
+
+ public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
+ mActivity = activity;
+ mTabHost = tabHost;
+ mContainerId = containerId;
+ mTabHost.setOnTabChangedListener(this);
+ }
+
+ public void addTab(TabHost.TabSpec tabSpec, Class> clss, Bundle args) {
+ tabSpec.setContent(new DummyTabFactory(mActivity));
+ String tag = tabSpec.getTag();
+
+ TabInfo info = new TabInfo(tag, clss, args);
+
+ // Check to see if we already have a fragment for this tab, probably
+ // from a previously saved state. If so, deactivate it, because our
+ // initial state is that a tab isn't shown.
+ info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
+ if (info.fragment != null && !info.fragment.isDetached()) {
+ FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
+ ft.detach(info.fragment);
+ ft.commit();
+ }
+
+ mTabs.put(tag, info);
+ mTabHost.addTab(tabSpec);
+ }
+
+ @Override
+ public void onTabChanged(String tabId) {
+ TabInfo newTab = mTabs.get(tabId);
+ if (mLastTab != newTab) {
+ FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
+ if (mLastTab != null) {
+ if (mLastTab.fragment != null) {
+ ft.detach(mLastTab.fragment);
+ }
+ }
+ if (newTab != null) {
+ if (newTab.fragment == null) {
+ newTab.fragment = Fragment.instantiate(mActivity, newTab.clss.getName(), newTab.args);
+ ft.add(mContainerId, newTab.fragment, newTab.tag);
+ } else {
+ ft.attach(newTab.fragment);
+ }
+ }
+
+ mLastTab = newTab;
+ ft.commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
index bfadbb872..50f230c3c 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackDetailActivity.java
@@ -36,7 +36,6 @@ import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -44,21 +43,21 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import android.speech.tts.TextToSpeech;
+import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.RelativeLayout;
+import android.widget.CheckBox;
+import android.widget.RadioGroup;
import android.widget.TabHost;
+import android.widget.TabHost.TabSpec;
import android.widget.Toast;
import java.util.List;
@@ -69,22 +68,24 @@ import java.util.List;
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
-@SuppressWarnings("deprecation")
-public class TrackDetailActivity extends TabActivity implements OnTouchListener {
+public class TrackDetailActivity extends FragmentActivity {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_WAYPOINT_ID = "waypoint_id";
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String MARKET_URL_PREFIX = "market://details?id=";
+ private static final String CURRENT_TAG_KEY = "tab";
private static final int DIALOG_INSTALL_EARTH_ID = 0;
private static final int DIALOG_DELETE_CURRENT_ID = 1;
-
+ private static final int DIALOG_CHART_SETTINGS_ID = 2;
+
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
- private NavControls navControls;
+ private TabHost tabHost;
+ private TabManager tabManager;
private long trackId;
private MenuItem stopRecordingMenuItem;
@@ -96,12 +97,8 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
private MenuItem editMenuItem;
private MenuItem deleteMenuItem;
- private final Runnable changeTab = new Runnable() {
- public void run() {
- getTabHost().setCurrentTab(navControls.getCurrentIcons());
- }
- };
-
+ private View mapViewContainer;
+
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
@@ -117,52 +114,66 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
}
};
+ /**
+ * We are not displaying driving directions. Just an arbitrary track that is
+ * not associated to any licensed mapping data. Therefore it should be okay to
+ * return false here and still comply with the terms of service.
+ */
+ @Override
+ protected boolean isRouteDisplayed() {
+ return false;
+ }
+
+ /**
+ * We are displaying a location. This needs to return true in order to comply
+ * with the terms of service.
+ */
+ @Override
+ protected boolean isLocationDisplayed() {
+ return true;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this);
+ setContentView(R.layout.track_detail);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
- final Resources res = getResources();
- final TabHost tabHost = getTabHost();
- tabHost.addTab(tabHost.newTabSpec(MAP_TAB_TAG)
- .setIndicator("Map", res.getDrawable(android.R.drawable.ic_menu_mapmode))
- .setContent(new Intent(this, MapActivity.class)));
- tabHost.addTab(tabHost.newTabSpec(STATS_TAB_TAG)
- .setIndicator("Stats", res.getDrawable(R.drawable.ic_menu_statistics))
- .setContent(new Intent(this, StatsActivity.class)));
- tabHost.addTab(tabHost.newTabSpec(CHART_TAB_TAG)
- .setIndicator("Chart", res.getDrawable(R.drawable.menu_elevation))
- .setContent(new Intent(this, ChartActivity.class)));
+ mapViewContainer = getLayoutInflater().inflate(R.layout.mytracks_layout, null);
+ tabHost = (TabHost) findViewById(android.R.id.tabhost);
+ tabHost.setup();
+ tabManager = new TabManager(this, tabHost, R.id.realtabcontent);
+ TabSpec mapTabSpec = tabHost.newTabSpec(MAP_TAB_TAG).setIndicator(
+ getString(R.string.track_detail_map_tab),
+ getResources().getDrawable(android.R.drawable.ic_menu_mapmode));
+ tabManager.addTab(mapTabSpec, MapFragment.class, null);
+ TabSpec chartTabSpec = tabHost.newTabSpec(CHART_TAB_TAG).setIndicator(
+ getString(R.string.track_detail_chart_tab),
+ getResources().getDrawable(R.drawable.menu_elevation));
+ tabManager.addTab(chartTabSpec, ChartFragment.class, null);
+ TabSpec statsTabSpec = tabHost.newTabSpec(STATS_TAB_TAG).setIndicator(
+ getString(R.string.track_detail_stats_tab),
+ getResources().getDrawable(R.drawable.ic_menu_statistics));
+ tabManager.addTab(statsTabSpec, StatsFragment.class, null);
+ if (savedInstanceState != null) {
+ tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAG_KEY));
+ }
- // Hide the tab widget itself. We'll use overlayed prev/next buttons to
- // switch between the tabs:
- tabHost.getTabWidget().setVisibility(View.GONE);
-
- RelativeLayout layout = new RelativeLayout(this);
- LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
- layout.setLayoutParams(params);
- navControls = new NavControls(this, layout, getResources().obtainTypedArray(R.array.left_icons),
- getResources().obtainTypedArray(R.array.right_icons), changeTab);
- navControls.show();
- tabHost.addView(layout);
- layout.setOnTouchListener(this);
-
handleIntent(getIntent());
}
-
+
@Override
- protected void onNewIntent(Intent intent) {
+ public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
- }
-
+ }
+
@Override
protected void onStart() {
super.onStart();
@@ -175,6 +186,12 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
trackRecordingServiceConnection.bindIfRunning();
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(CURRENT_TAG_KEY, tabHost.getCurrentTabTag());
+ }
+
@Override
protected void onStop() {
super.onStop();
@@ -212,11 +229,55 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
startTrackListActivity();
}
});
+ case DIALOG_CHART_SETTINGS_ID:
+ return createChartSettingsDialog();
default:
return null;
}
}
+ private Dialog createChartSettingsDialog() {
+ final ChartFragment chartFragment = (ChartFragment) getSupportFragmentManager()
+ .findFragmentByTag(CHART_TAB_TAG);
+ View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
+ final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
+ radioGroup
+ .check(chartFragment.getMode() == ChartView.Mode.BY_DISTANCE ? R.id.chart_settings_by_distance
+ : R.id.chart_settings_by_time);
+
+ final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
+ checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view
+ .findViewById(R.id.chart_settings_elevation);
+ checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
+ checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
+ checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view
+ .findViewById(R.id.chart_settings_cadence);
+ checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view
+ .findViewById(R.id.chart_settings_heart_rate);
+
+ // set checkboxes values
+ for (int i = 0; i < ChartView.NUM_SERIES; i++) {
+ checkBoxes[i].setChecked(chartFragment.isChartValueSeriesEnabled(i));
+ }
+ checkBoxes[ChartView.SPEED_SERIES].setText(chartFragment.isReportSpeed() ? R.string.stat_speed
+ : R.string.stat_pace);
+
+ return new AlertDialog.Builder(this).setCancelable(true)
+ .setNegativeButton(R.string.generic_cancel, null)
+ .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ chartFragment
+ .setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance ? ChartView.Mode.BY_DISTANCE
+ : ChartView.Mode.BY_TIME);
+ for (int i = 0; i < ChartView.NUM_SERIES; i++) {
+ chartFragment.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
+ }
+ chartFragment.update();
+ }
+ }).setTitle(R.string.menu_chart_settings).setView(view).create();
+ }
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_detail, menu);
@@ -254,13 +315,14 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
- String currentTabTag = getTabHost().getCurrentTabTag();
+ String currentTabTag = tabHost.getCurrentTabTag();
menu.findItem(R.id.track_detail_chart_settings).setVisible(CHART_TAB_TAG.equals(currentTabTag));
menu.findItem(R.id.track_detail_my_location).setVisible(MAP_TAB_TAG.equals(currentTabTag));
// Set map or satellite mode
- MapActivity mapActivity = getMapActivity();
- boolean isSatelliteMode = mapActivity != null ? mapActivity.isSatelliteView() : false;
+ MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
+ .findFragmentByTag(MAP_TAB_TAG);
+ boolean isSatelliteMode = mapFragment != null ? mapFragment.isSatelliteView() : false;
menu.findItem(R.id.track_detail_satellite_mode).setVisible(MAP_TAB_TAG.equals(currentTabTag))
.setTitle(isSatelliteMode ? R.string.menu_map_mode : R.string.menu_satellite_mode);
@@ -269,7 +331,7 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- MapActivity mapActivity;
+ MapFragment mapFragment;
Intent intent;
switch (item.getItemId()) {
case android.R.id.home:
@@ -346,22 +408,19 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
showDialog(DIALOG_DELETE_CURRENT_ID);
return true;
case R.id.track_detail_my_location:
- mapActivity = getMapActivity();
- if (mapActivity != null) {
- mapActivity.showMyLocation();
+ mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
+ if (mapFragment != null) {
+ mapFragment.showMyLocation();
}
return true;
case R.id.track_detail_satellite_mode:
- mapActivity = getMapActivity();
- if (mapActivity != null) {
- mapActivity.setSatelliteView(!mapActivity.isSatelliteView());
+ mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
+ if (mapFragment != null) {
+ mapFragment.setSatelliteView(!mapFragment.isSatelliteView());
}
return true;
case R.id.track_detail_chart_settings:
- ChartActivity chartActivity = getChartActivity();
- if (chartActivity != null) {
- chartActivity.showChartSettingsDialog();
- }
+ showDialog(DIALOG_CHART_SETTINGS_ID);
return true;
case R.id.track_detail_sensor_state:
startActivity(new Intent(this, SensorStateActivity.class));
@@ -386,10 +445,11 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
if (results != null) {
long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1L);
if (waypointId != -1L) {
- MapActivity mapActivity = (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
- if (mapActivity != null) {
- getTabHost().setCurrentTab(0);
- mapActivity.showWaypoint(waypointId);
+ MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
+ .findFragmentByTag(MAP_TAB_TAG);
+ if (mapFragment != null) {
+ tabHost.setCurrentTab(0);
+ mapFragment.showWaypoint(waypointId);
}
}
}
@@ -425,12 +485,11 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
return super.onTrackballEvent(event);
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- navControls.show();
- }
- return false;
+ /**
+ * @return the mapViewContainer
+ */
+ public View getMapViewContainer() {
+ return mapViewContainer;
}
/**
@@ -444,16 +503,17 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
finish();
}
trackDataHub.loadTrack(trackId);
-
+
// Get the waypointId
long waypointId = intent.getLongExtra(EXTRA_WAYPOINT_ID, -1L);
if (waypointId != -1L) {
- MapActivity mapActivity = getMapActivity();
- if (mapActivity != null) {
- getTabHost().setCurrentTab(0);
- mapActivity.showWaypoint(trackId, waypointId);
+ MapFragment mapFragmet = (MapFragment) getSupportFragmentManager()
+ .findFragmentByTag(MAP_TAB_TAG);
+ if (mapFragmet != null) {
+ tabHost.setCurrentTab(0);
+ mapFragmet.showWaypoint(trackId, waypointId);
} else {
- Log.e(TAG, "MapActivity is null");
+ Log.e(TAG, "MapFragment is null");
}
}
}
@@ -502,7 +562,6 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
* Deletes the current track.
*/
private void deleteCurrentTrack() {
- long trackId = trackDataHub.getSelectedTrackId();
MyTracksProviderUtils.Factory.get(TrackDetailActivity.this).deleteTrack(trackId);
}
@@ -575,18 +634,4 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
}
return false;
}
-
- /**
- * Gets the map activity, can be null.
- */
- private MapActivity getMapActivity() {
- return (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
- }
-
- /**
- * Gets the chart activity, can be null.
- */
- private ChartActivity getChartActivity() {
- return (ChartActivity) getLocalActivityManager().getActivity(CHART_TAB_TAG);
- }
}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
index 81a499826..22a373a29 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/TrackListActivity.java
@@ -39,6 +39,7 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.Fragment;
+import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java b/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java
index 2287a4be3..86ef70e25 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGeneratorImpl.java
@@ -116,8 +116,8 @@ public class DescriptionGeneratorImpl implements DescriptionGenerator {
// Average moving pace
writePace(averageMovingSpeed, builder, R.string.description_average_moving_pace, lineBreak);
- // Min pace
- writePace(maxSpeed, builder, R.string.description_min_pace, lineBreak);
+ // Fastest pace
+ writePace(maxSpeed, builder, R.string.description_fastest_pace, lineBreak);
// Max elevation
writeElevation(stats.getMaxElevation(), builder, R.string.description_max_elevation, lineBreak);
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
similarity index 54%
rename from MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
rename to MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
index 1ed95955a..69418ddd6 100644
--- a/MyTracksTest/src/com/google/android/apps/mytracks/ChartActivityTest.java
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/ChartFragmentTest.java
@@ -13,152 +13,100 @@
* 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.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.util.UnitConversions;
-import com.google.android.maps.mytracks.R;
-import android.app.Instrumentation;
import android.location.Location;
-import android.test.ActivityInstrumentationTestCase2;
-import android.view.View;
-import android.widget.ZoomControls;
+import android.test.AndroidTestCase;
/**
- * Tests {@link ChartActivity}.
- *
+ * Tests {@link ChartFragment}.
+ *
* @author Youtao Liu
*/
-public class ChartActivityTest extends ActivityInstrumentationTestCase2 {
+public class ChartFragmentTest extends AndroidTestCase {
- private Instrumentation instrumentation;
- private ChartActivity chartActivity;
- private View zoomIn;
- private View zoomOut;
- private int currentZoomLevel;
+ private ChartFragment chartFragment;
- // 10 is same with the default value in ChartView
- private final int MAX_ZOOM_LEVEL = 10;
- private final int MIN_ZOOM_LEVEL = 1;
- // The ratio from meter/second to kilometer/hour, the conversion is 60 * 60 /
- // 1000 = 3.6.
- private final double METER_PER_SECOND_TO_KILOMETER_PER_HOUR = 3.6;
- private final double KILOMETER_TO_METER = 1000.0;
- private final double HOURS_PER_UNIT = 60;
-
- public ChartActivityTest() {
- super(ChartActivity.class);
- }
+ private final double HOURS_PER_UNIT = 60.0;
@Override
protected void setUp() throws Exception {
- super.setUp();
- instrumentation = getInstrumentation();
- chartActivity = getActivity();
+ chartFragment = new ChartFragment();
+ chartFragment.setChartView(new ChartView(getContext()));
}
/**
- * Tests zooming in and zooming out.
- */
- public void testZoomInAndZoomOut() {
- currentZoomLevel = chartActivity.getChartView().getZoomLevel();
- chartActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ZoomControls zoomControls = (ZoomControls) chartActivity.findViewById(R.id.elevation_zoom);
- zoomIn = zoomControls.getChildAt(0);
- zoomOut = zoomControls.getChildAt(1);
- // Invoke following two methods method to initial the display of
- // ZoomControls.
- zoomControls.setIsZoomInEnabled(chartActivity.getChartView().canZoomIn());
- zoomControls.setIsZoomOutEnabled(chartActivity.getChartView().canZoomOut());
- // Click zoomIn button to disable.
- for (int i = currentZoomLevel; i > MIN_ZOOM_LEVEL; i--) {
- zoomIn.performClick();
- }
- }
- });
- instrumentation.waitForIdleSync();
- assertEquals(false, zoomIn.isEnabled());
- assertEquals(true, zoomOut.isEnabled());
- chartActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- // Click to the second max zoom level
- for (int i = MIN_ZOOM_LEVEL; i < MAX_ZOOM_LEVEL - 1; i++) {
- zoomOut.performClick();
- assertEquals(true, zoomIn.isEnabled());
- assertEquals(true, zoomOut.isEnabled());
- }
- zoomOut.performClick();
- }
- });
- instrumentation.waitForIdleSync();
- assertEquals(true, zoomIn.isEnabled());
- assertEquals(false, zoomOut.isEnabled());
- }
-
- /**
- * Tests the logic to get the incorrect values of sensor in {@link
- * ChartActivity#fillDataPoint(Location, double[])}.
+ * Tests the logic to get the incorrect values of sensor in
+ * {@link ChartFragment#fillDataPoint(Location, double[])}
*/
public void testFillDataPoint_sensorIncorrect() {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
+
// No input.
- double[] point = fillDataPointTestHelper(myTracksLocation, false);
+ double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
// Input incorrect state.
// Creates SensorData.
- Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.NONE);
- Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.NONE);
- Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.NONE);
+ Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.NONE);
+ Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.NONE);
+ Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.NONE);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
- sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
- .setHeartRate(heartRateData).build();
+ sensorDataSet = sensorDataSet.toBuilder()
+ .setPower(powerData)
+ .setCadence(cadenceData)
+ .setHeartRate(heartRateData)
+ .build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
- point = fillDataPointTestHelper(myTracksLocation, false);
+ point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
}
/**
- * Tests the logic to get the correct values of sensor in {@link
- * ChartActivity#fillDataPoint(Location, double[])}.
+ * Tests the logic to get the correct values of sensor in
+ * {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
public void testFillDataPoint_sensorCorrect() {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
// No input.
- double[] point = fillDataPointTestHelper(myTracksLocation, false);
+ double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
// Creates SensorData.
- Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.SENDING);
- Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.SENDING);
- Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder().setValue(20)
- .setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.SENDING);
+ Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
+ .setValue(20).setState(Sensor.SensorState.SENDING);
+
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
- sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
- .setHeartRate(heartRateData).build();
+ sensorDataSet = sensorDataSet.toBuilder()
+ .setPower(powerData)
+ .setCadence(cadenceData)
+ .setHeartRate(heartRateData)
+ .build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
- point = fillDataPointTestHelper(myTracksLocation, false);
+ point = fillDataPointTestHelper(myTracksLocation);
assertEquals(20.0, point[3]);
assertEquals(20.0, point[4]);
assertEquals(20.0, point[5]);
@@ -166,61 +114,63 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2