merge heads

This commit is contained in:
Jimmy Shih
2013-11-20 10:36:47 -08:00
parent 45909e70b6
commit 63c1330f50
68 changed files with 1487 additions and 1176 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:duration="1000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/linear_interpolator"
android:toAlpha="0.0" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

+144
View File
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2013 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Distance, calorie, total time, and moving time -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_distance"
layout="@layout/stats_large_item" />
<include
android:id="@+id/stats_calorie"
layout="@layout/stats_calorie" />
<View
android:layout_height="match_parent"
android:layout_width="1dp"
android:visibility="invisible" />
<include
android:id="@+id/stats_total_time"
layout="@layout/stats_large_item" />
<include
android:id="@+id/stats_moving_time"
layout="@layout/stats_large_item" />
</LinearLayout>
<!-- Horizontal line -->
<View style="@style/StatsHorizontalLine" />
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<TableLayout
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="0dp" >
<!-- Speed and average speed -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Max speed and average moving speed -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_max_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_moving_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Latitude and longitude -->
<LinearLayout
android:id="@+id/stats_coordinate_container"
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_latitude"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_longitude"
layout="@layout/stats_small_item" />
</LinearLayout>
</TableLayout>
<!-- Vertical line -->
<View
android:background="@color/stats_separator"
android:id="@+id/stats_grade_elevation_separator"
android:layout_height="match_parent"
android:layout_width="1dp" />
<LinearLayout
android:id="@+id/stats_grade_elevation_container"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="0dp" >
<!-- Grade -->
<LinearLayout style="@style/StatsSmallGroupContainer" >
<!-- Grade label -->
<TextView
android:text="@string/stats_grade"
style="@style/StatsSmallLabel" />
<TableLayout
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" >
<!-- Grade min -->
<include
android:id="@+id/stats_grade_min"
layout="@layout/stats_small_group_item" />
<!-- Grade max -->
<include
android:id="@+id/stats_grade_max"
layout="@layout/stats_small_group_item" />
</TableLayout>
</LinearLayout>
<!-- Elevation -->
<LinearLayout style="@style/StatsSmallGroupContainer" >
<!-- Elevation label -->
<TextView
android:text="@string/stats_elevation"
style="@style/StatsSmallLabel" />
<TableLayout
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" >
<!-- Elevation -->
<include
android:id="@+id/stats_elevation"
layout="@layout/stats_small_group_item" />
<!-- Elevation gain -->
<include
android:id="@+id/stats_elevation_gain"
layout="@layout/stats_small_group_item" />
<!-- Elevation min -->
<include
android:id="@+id/stats_elevation_min"
layout="@layout/stats_small_group_item" />
<!-- Elevation max -->
<include
android:id="@+id/stats_elevation_max"
layout="@layout/stats_small_group_item" />
</TableLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</merge>
+1 -2
View File
@@ -17,8 +17,7 @@ limitations under the License.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:baselineAligned="false"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal" >
android:layout_width="match_parent" >
<include layout="@layout/track_controller" />
<TabHost
android:id="@android:id/tabhost"
+1 -2
View File
@@ -16,8 +16,7 @@ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal" >
android:layout_width="match_parent" >
<include layout="@layout/track_controller" />
<ListView
android:id="@+id/track_list"
@@ -19,7 +19,6 @@ limitations under the License.
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingRight="16dp" >
<ImageView
+49 -5
View File
@@ -14,10 +14,54 @@ 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.
-->
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:numColumns="4"
android:padding="4dp"
android:stretchMode="spacingWidth"
android:verticalSpacing="4dp" />
android:orientation="vertical" >
<GridView
android:id="@+id/choose_activity_type_grid_view"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:numColumns="4"
android:padding="4dp"
android:stretchMode="spacingWidth"
android:verticalSpacing="4dp" />
<LinearLayout
android:id="@+id/choose_activity_type_weight_container"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/choose_activity_type_weight_label"
android:layout_marginBottom="2dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="4dp"
android:paddingLeft="12dp"
style="@style/TextMedium" />
<FrameLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent" >
<EditText
android:id="@+id/choose_activity_type_weight"
android:inputType="numberDecimal"
android:layout_marginBottom="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="2dp"
android:layout_width="match_parent"
android:selectAllOnFocus="true"
android:singleLine="true"
style="@style/TextMedium" />
<ImageButton
android:background="@android:color/transparent"
android:id="@+id/choose_activity_type_weight_image_button"
android:layout_gravity="right|center_vertical"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_width="wrap_content"
android:paddingRight="12dp"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
+96 -92
View File
@@ -24,103 +24,107 @@ limitations under the License.
android:layout_height="match_parent"
android:layout_width="match_parent"
android:scaleType="centerCrop" />
<!-- A gradient on top of the text -->
<ImageView
android:alpha=".6"
android:contentDescription="@string/icon_marker"
android:id="@+id/list_item_text_gradient"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:src="@drawable/text_gradient" />
<!-- Table 1 -->
<TableLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp"
android:shrinkColumns="1"
android:stretchColumns="1"
style="@style/ListItem" >
<!-- Table 1: Row 1 -->
<TableRow>
<!-- Table 1: Row 1: Column 1: (icon) -->
<ImageView
android:contentDescription="@string/icon_track"
android:id="@+id/list_item_icon"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="wrap_content" />
<!-- Table 1: Row 1: Column 2: (Table 2) -->
<TableLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:shrinkColumns="0"
android:stretchColumns="0" >
<TableRow>
<!-- Table 2: Row 1: Column 1: (name) -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_name"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:singleLine="true"
style="@style/TextMedium" />
<!-- Table 2: Row 1: Column 2: (date) -->
<TextView
android:id="@+id/list_item_date"
style="@style/ListItemDate" />
</TableRow>
<TableRow>
<!-- Need to match the style set in ListItemUtils.setListItem -->
<!-- Table 2: Row 2: Column 1: (shared owner, total time, total distance, and marker count) -->
<LinearLayout
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<FrameLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<!-- A gradient on top of the text -->
<ImageView
android:alpha=".6"
android:contentDescription="@string/icon_marker"
android:id="@+id/list_item_text_gradient"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:src="@drawable/text_gradient" />
<!-- Table 1 -->
<TableLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingBottom="12dp"
android:paddingRight="12dp"
android:paddingTop="8dp"
android:shrinkColumns="1"
android:stretchColumns="1"
style="@style/ListItem" >
<!-- Table 1: Row 1 -->
<TableRow>
<!-- Table 1: Row 1: Column 1: (icon) -->
<ImageView
android:contentDescription="@string/icon_track"
android:id="@+id/list_item_icon"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="wrap_content" />
<!-- Table 1: Row 1: Column 2: (Table 2) -->
<TableLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:shrinkColumns="0"
android:stretchColumns="0" >
<TableRow>
<!-- Table 2: Row 1: Column 1: (name) -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_owner_time_distance"
android:id="@+id/list_item_name"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:singleLine="true"
style="@style/TextSmall" />
<ImageView
android:contentDescription="@string/icon_marker"
android:id="@+id/list_item_marker_count_icon"
style="@style/TextMedium" />
<!-- Table 2: Row 1: Column 2: (date) -->
<TextView
android:id="@+id/list_item_date"
style="@style/ListItemDate" />
</TableRow>
<TableRow>
<!-- Need to match the style set in ListItemUtils.setListItem -->
<!-- Table 2: Row 2: Column 1: (shared owner, total time, total distance, and marker count) -->
<LinearLayout
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="1dp"
android:layout_width="wrap_content"
android:scaleType="centerInside"
android:src="@drawable/ic_menu_insert_marker" />
android:layout_width="match_parent" >
<TextView
android:ellipsize="end"
android:id="@+id/list_item_owner_time_distance"
android:layout_gravity="bottom"
android:singleLine="true"
style="@style/TextSmall" />
<ImageView
android:contentDescription="@string/icon_marker"
android:id="@+id/list_item_marker_count_icon"
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_width="wrap_content"
android:scaleType="centerInside"
android:src="@drawable/ic_menu_insert_marker" />
<TextView
android:ellipsize="end"
android:id="@+id/list_item_marker_count"
android:layout_gravity="bottom"
android:singleLine="true"
style="@style/TextSmall" />
</LinearLayout>
<!-- Table 2: Row 2: Column 2: (time) -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_marker_count"
android:layout_gravity="bottom"
android:singleLine="true"
style="@style/TextSmall" />
</LinearLayout>
<!-- Table 2: Row 2: Column 2: (time) -->
<TextView
android:id="@+id/list_item_time"
style="@style/ListItemDate" />
</TableRow>
</TableLayout>
</TableRow>
<TableRow>
<!-- Table 1: Row 2: (category and description) -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_category_description"
android:layout_column="1"
android:maxLines="2"
android:singleLine="false"
style="@style/TextSmall" />
</TableRow>
</TableLayout>
android:id="@+id/list_item_time"
style="@style/ListItemDate" />
</TableRow>
</TableLayout>
</TableRow>
<TableRow>
<!-- Table 1: Row 2: (category and description) -->
<TextView
android:ellipsize="end"
android:id="@+id/list_item_category_description"
android:layout_column="1"
android:maxLines="2"
android:singleLine="false"
style="@style/TextSmall" />
</TableRow>
</TableLayout>
</FrameLayout>
</FrameLayout>
+1 -3
View File
@@ -19,9 +19,7 @@ limitations under the License.
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp" >
android:orientation="vertical">
<include layout="@layout/stats_content" />
</LinearLayout>
</ScrollView>
@@ -15,17 +15,28 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stats_small_1line_id"
style="@style/StatsSmallValueContainer" >
<TextView
android:id="@+id/stats_value"
style="@style/StatsSmallValue" />
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit" />
style="@style/StatsLargeItemContainer" >
<!-- Label -->
<TextView
android:id="@+id/stats_label"
android:layout_gravity="bottom"
android:paddingLeft="8dp"
style="@style/StatsSmallLabel" />
style="@style/StatsLargeLabel" />
<!-- Value container -->
<LinearLayout style="@style/StatsLargeValueContainer" >
<!-- Value -->
<TextView
android:id="@+id/stats_value"
style="@style/StatsLargeValue" />
<!-- Unit -->
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit" />
<!-- Activity type -->
<Spinner
android:id="@+id/stats_activity_type_icon"
android:layout_gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginTop="-6dp"
android:layout_width="wrap_content" />
</LinearLayout>
</LinearLayout>
+63 -79
View File
@@ -15,108 +15,114 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Row 1: distance and speed -->
<!-- Distance and calorie -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_distance"
layout="@layout/stats_large_item" />
<View style="@style/StatsVerticalSpacer" />
<include
android:id="@+id/stats_speed"
layout="@layout/stats_large_item" />
android:id="@+id/stats_calorie"
layout="@layout/stats_calorie" />
</LinearLayout>
<!-- Row 2: total time and average speed -->
<!-- Total time and moving time -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_total_time"
layout="@layout/stats_large_item" />
<View style="@style/StatsVerticalSpacer" />
<include
android:id="@+id/stats_average_speed"
layout="@layout/stats_large_item" />
</LinearLayout>
<!-- Row 3: horizontal line -->
<View style="@style/StatsHorizontalLine" />
<!-- Row 3: moving time and average moving speed -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_moving_time"
layout="@layout/stats_small_item" />
<View style="@style/StatsVerticalSpacer" />
<include
android:id="@+id/stats_average_moving_time"
layout="@layout/stats_small_item" />
layout="@layout/stats_large_item" />
</LinearLayout>
<!-- Row 4: empty and max speed -->
<!-- Horizontal line -->
<View style="@style/StatsHorizontalLine" />
<!-- Speed and average speed -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<include
android:id="@+id/stats_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Max speed and average moving speed -->
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<LinearLayout style="@style/StatsSmallItemContainer" />
<View style="@style/StatsVerticalSpacer" />
<include
android:id="@+id/stats_max_speed"
layout="@layout/stats_small_item" />
<include
android:id="@+id/stats_average_moving_speed"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Row 5: horizontal line -->
<!-- Horizontal line -->
<View
android:id="@+id/stats_grade_elevation_horizontal_line"
android:id="@+id/stats_grade_elevation_separator"
style="@style/StatsHorizontalLine" />
<!-- Row 5: grade and elevation -->
<LinearLayout
android:id="@+id/stats_grade_elevation_container"
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<!-- Grade -->
<LinearLayout style="@style/StatsSmallItemContainer" >
<LinearLayout style="@style/StatsSmallGroupContainer" >
<!-- Grade label -->
<TextView
android:text="@string/stats_grade"
style="@style/StatsSmallLabel" />
<!-- Grade min -->
<include
android:id="@+id/stats_grade_min"
layout="@layout/stats_small_item_1line" />
<!-- Grade max -->
<include
android:id="@+id/stats_grade_max"
layout="@layout/stats_small_item_1line" />
<TableLayout
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" >
<!-- Grade min -->
<include
android:id="@+id/stats_grade_min"
layout="@layout/stats_small_group_item" />
<!-- Grade max -->
<include
android:id="@+id/stats_grade_max"
layout="@layout/stats_small_group_item" />
</TableLayout>
</LinearLayout>
<View style="@style/StatsVerticalSpacer" />
<!-- Elevation -->
<LinearLayout style="@style/StatsSmallItemContainer" >
<LinearLayout style="@style/StatsSmallGroupContainer" >
<!-- Elevation label -->
<TextView
android:text="@string/stats_elevation"
style="@style/StatsSmallLabel" />
<!-- Elevation -->
<include
android:id="@+id/stats_elevation"
layout="@layout/stats_small_item_1line" />
<!-- Elevation gain -->
<include
android:id="@+id/stats_elevation_gain"
layout="@layout/stats_small_item_1line" />
<!-- Elevation min -->
<include
android:id="@+id/stats_elevation_min"
layout="@layout/stats_small_item_1line" />
<!-- Elevation max -->
<include
android:id="@+id/stats_elevation_max"
layout="@layout/stats_small_item_1line" />
<TableLayout
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" >
<!-- Elevation -->
<include
android:id="@+id/stats_elevation"
layout="@layout/stats_small_group_item" />
<!-- Elevation gain -->
<include
android:id="@+id/stats_elevation_gain"
layout="@layout/stats_small_group_item" />
<!-- Elevation min -->
<include
android:id="@+id/stats_elevation_min"
layout="@layout/stats_small_group_item" />
<!-- Elevation max -->
<include
android:id="@+id/stats_elevation_max"
layout="@layout/stats_small_group_item" />
</TableLayout>
</LinearLayout>
</LinearLayout>
<!-- Row 6: horizontal line -->
<!-- Horizontal line -->
<View
android:id="@+id/stats_coordinate_horizontal_line"
android:id="@+id/stats_coordinate_separator"
style="@style/StatsHorizontalLine" />
<!-- Row 6: latitude and longitude -->
<!-- Latitude and longitude -->
<LinearLayout
android:id="@+id/stats_coordinate_container"
android:layout_height="wrap_content"
@@ -124,30 +130,8 @@ limitations under the License.
<include
android:id="@+id/stats_latitude"
layout="@layout/stats_small_item" />
<View style="@style/StatsVerticalSpacer" />
<include
android:id="@+id/stats_longitude"
layout="@layout/stats_small_item" />
</LinearLayout>
<!-- Row 7: calorie horizontal line -->
<View
android:id="@+id/stats_calorie_horizontal_line"
style="@style/StatsHorizontalLine" />
<!-- Row 7: calorie -->
<LinearLayout
android:id="@+id/stats_calorie_container"
android:layout_height="wrap_content"
android:layout_width="match_parent" >
<!-- calorie value -->
<include
android:id="@+id/stats_calorie"
layout="@layout/stats_small_item" />
<!-- Setup activity type-->
<Button
android:id="@+id/stats_calorie_setup"
android:layout_gravity="right"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/stats_calorie_setup" />
</LinearLayout>
</merge>
+4 -3
View File
@@ -15,17 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stats_large_item_id"
style="@style/StatsLargeItemContainer" >
<!-- Distance label -->
<!-- Label -->
<TextView
android:id="@+id/stats_label"
style="@style/StatsLargeLabel" />
<!-- Distance value -->
<!-- Value container -->
<LinearLayout style="@style/StatsLargeValueContainer" >
<!-- Value -->
<TextView
android:id="@+id/stats_value"
style="@style/StatsLargeValue" />
<!-- Unit -->
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit" />
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2013 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.
-->
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content" >
<!-- Value container -->
<LinearLayout
android:layout_gravity="right|bottom"
style="@style/StatsSmallValueContainer" >
<!-- Value -->
<TextView
android:id="@+id/stats_value"
style="@style/StatsSmallValue" />
<!-- Unit -->
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit" />
</LinearLayout>
<!-- Label container -->
<LinearLayout
android:layout_gravity="left|bottom"
style="@style/StatsSmallValueContainer" >
<TextView
android:visibility="invisible"
style="@style/StatsSmallValue" />
<!-- Label -->
<TextView
android:id="@+id/stats_label"
android:layout_gravity="bottom"
android:paddingLeft="8dp"
style="@style/StatsSmallLabel" />
</LinearLayout>
</TableRow>
+4 -2
View File
@@ -15,16 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/stats_small_item_id"
style="@style/StatsSmallItemContainer" >
<!-- Label -->
<TextView
android:id="@+id/stats_label"
style="@style/StatsSmallLabel" />
<!-- Distance value -->
<!-- Value container -->
<LinearLayout style="@style/StatsSmallValueContainer" >
<!-- Value -->
<TextView
android:id="@+id/stats_value"
style="@style/StatsSmallValue" />
<!-- Unit -->
<TextView
android:id="@+id/stats_unit"
style="@style/StatsUnit" />
-1
View File
@@ -20,7 +20,6 @@ limitations under the License.
android:id="@+id/track_controler_container"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingTop="8dp" >
<View
+1 -2
View File
@@ -23,8 +23,7 @@ limitations under the License.
android:id="@android:id/tabs"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_width="match_parent"
android:orientation="horizontal" />
android:layout_width="match_parent" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_height="0dp"
+1 -2
View File
@@ -38,8 +38,7 @@ limitations under the License.
style="@style/EditLabel.WithPaddingTop" />
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="horizontal" >
android:layout_width="match_parent" >
<AutoCompleteTextView
android:gravity="bottom"
android:hint="@string/track_edit_activity_type_hint"
-1
View File
@@ -17,7 +17,6 @@ limitations under the License.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/TrackWidgetBoundingBox" >
<LinearLayout
android:orientation="horizontal"
android:paddingBottom="4dp"
android:paddingTop="4dp"
style="@style/TrackWidgetFrame" >
-1
View File
@@ -34,7 +34,6 @@ limitations under the License.
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<item name="android:measureWithLargestChild">true</item>
<item name="android:orientation">horizontal</item>
</style>
<style name="BottomButtonDivider">
<item name="android:background">?android:attr/dividerVertical</item>
+7 -7
View File
@@ -18,22 +18,22 @@ limitations under the License.
<!-- The style for choose activity text -->
<style name="StatsSmallLabel" parent="@style/TextSmall">
<!-- same as StatsSmallLabel in values/styles.xml except adding textAllCaps -->
<item name="android:gravity">right</item>
<item name="android:layout_gravity">right</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
<item name="android:textAllCaps">true</item>
<item name="android:textStyle">bold</item>
</style>
</style>
<style name="StatsLargeLabel" parent="@style/TextSmall">
<!-- same as StatsLargeLabel in values/styles.xml except adding textAllCaps -->
<item name="android:gravity">right</item>
<item name="android:layout_gravity">right</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
<item name="android:textAllCaps">true</item>
<item name="android:textColor">@color/stats_large_label</item>
<item name="android:textStyle">bold</item>
</style>
<style name="TextChooseActivity">
</style>
<style name="TextChooseActivity">
<!-- Same as TextChooseActivity in values/styles.xml except removing textSize and using textAppearance -->
<item name="android:textAppearance">?android:attr/textAppearanceListItemSmall</item>
</style>
+1 -2
View File
@@ -42,8 +42,6 @@ limitations under the License.
<string name="stats_rate_key">statsRate</string>
<string name="stats_show_coordinate_key">statsShowCoordinate</string>
<string name="stats_show_grade_elevation_key">statsShowGradeElevation</string>
<string name="stats_show_calorie_key">statsShowCalorie</string>
<string name="stats_weight_key">statsWeight</string>
<string name="stats_units_key">statsUnits</string>
<string name="track_color_mode_medium_key">trackColorModeMedium</string>
<string name="track_color_mode_percentage_key">trackColorModePercentage</string>
@@ -70,6 +68,7 @@ limitations under the License.
<string name="google_account_key">googleAccount</string>
<string name="recording_track_id_key">recordingTrackId</string>
<string name="recording_track_paused_key">recordingTrackPaused</string>
<string name="weight_key">weight</string>
<!-- Keys for non-persisted preferences. Sorted alphabetically. DO NOT TRANSLATE. -->
<string name="settings_advanced_key">settingsAdvanced</string>
<string name="settings_backup_key">settingsBackup</string>
+13 -27
View File
@@ -555,7 +555,15 @@ limitations under the License.
translation_description="Label for a track segment">
Segment
</string>
<string name="description_weight_imperial"
translation_description="Label for weight in pounds.">
Weight (lb)
</string>
<string name="description_weight_metric"
translation_description="Label for weight in kilograms.">
Weight (kg)
</string>
<!-- Eula -->
<string name="eula_accept"
translation_description="In the 'Terms of Service' dialog, the button to accept.">
@@ -2025,16 +2033,6 @@ limitations under the License.
Character limit: ~23 (1 line)">
Grade/Elevation
</string>
<string name="settings_stats_calorie_weight"
translation_description="In the 'Stats settings, the option to configure user's weight.
Character limit: ~23 (1 line)">
Weight
</string>
<string name="settings_stats_calorie_weight_description"
translation_description="In the 'Stats settings, the description message about weight setting item.">
Weight for calculating calories.
</string>
<string name="settings_stats_rate_title"
translation_description="In the 'Stats' settings, the option to change the preferred rate,
either speed (mi/hr) or pace (min/mi).
@@ -2157,11 +2155,11 @@ limitations under the License.
Character limit: ~23 (1 line)">
Calories
</string>
<string name="stats_calorie_setup"
translation_description="In the 'Stats' page, the button to setup calorie.
<string name="stats_calorie_no_calculation"
translation_description="In the 'Stats' page, the message when calories cannot be calculated.
Character limit: ~23 (1 line)">
Setup Activity Type
%1$s activity type. E.g., 'driving'.">
Calories can\'t be calculated with %1$s.
</string>
<string name="stats_fastest_pace"
translation_description="In the 'Stats' page, the fastest pace.">
@@ -2514,12 +2512,6 @@ limitations under the License.
%1$d an integer value. E.g., '2'.">
%1$d kB
</string>
<string name="value_integer_kilogram"
translation_description="Display an integer value in kilogram (kg).
%1$d an integer value. E.g., '2'.">
%1$d kg
</string>
<string name="value_integer_kilometer"
translation_description="In a setting picker, the string format to display an integer value in
kilometers (km).
@@ -2594,12 +2586,6 @@ limitations under the License.
%1$d an integer value. E.g., '2'.">
%1$d %%
</string>
<string name="value_integer_pound"
translation_description="Display an integer value in pound (lb).
%1$d an integer value. E.g., '2'.">
%1$d lb
</string>
<string name="value_integer_second"
translation_description="In a setting picker, the string format to display an integer value in
seconds (s).
+23 -26
View File
@@ -30,7 +30,6 @@ limitations under the License.
<style name="BottomButtonBar">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<item name="android:orientation">horizontal</item>
</style>
<!-- bottom button divider -->
<style name="BottomButtonDivider">
@@ -84,7 +83,7 @@ limitations under the License.
<item name="android:layout_marginBottom">8dp</item>
<item name="android:layout_marginLeft">20dp</item>
<item name="android:layout_width">match_parent</item>
</style>
</style>
<!-- help answer -->
<style name="HelpAnswer" parent="@style/TextSmall">
<item name="android:autoLink">web</item>
@@ -133,30 +132,31 @@ limitations under the License.
<style name="StatsHorizontalLine">
<item name="android:background">@color/stats_separator</item>
<item name="android:layout_height">1dp</item>
<item name="android:layout_marginLeft">8dp</item>
<item name="android:layout_marginRight">8dp</item>
<item name="android:layout_width">match_parent</item>
</style>
<!-- stats large item container -->
<style name="StatsLargeItemContainer">
<item name="android:layout_gravity">top</item>
<item name="android:layout_gravity">center</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">10</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
<item name="android:orientation">vertical</item>
<item name="android:paddingBottom">6dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:paddingRight">8dp</item>
<item name="android:paddingTop">6dp</item>
</style>
<!-- stats large label -->
<style name="StatsLargeLabel" parent="@style/TextSmall">
<item name="android:gravity">right</item>
<item name="android:layout_gravity">right</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
<item name="android:textColor">@color/stats_large_label</item>
<item name="android:textStyle">bold</item>
</style>
<!-- stats large value -->
<style name="StatsLargeValue">
<item name="android:layout_gravity">bottom</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:singleLine">true</item>
@@ -166,7 +166,7 @@ limitations under the License.
</style>
<!-- stats large value container -->
<style name="StatsLargeValueContainer">
<item name="android:layout_gravity">right</item>
<item name="android:layout_gravity">center</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">-4dp</item>
<item name="android:layout_width">wrap_content</item>
@@ -174,20 +174,26 @@ limitations under the License.
<!-- stats scroll view -->
<style name="StatsScrollView" parent="@style/ScrollView">
<item name="android:scrollbarStyle">outsideOverlay</item>
</style>
<!-- stats small item container -->
</style>
<!-- stats small group container -->
<style name="StatsSmallGroupContainer" parent="@style/StatsLargeItemContainer">
<item name="android:layout_gravity">top|center</item>
<item name="android:paddingTop">12dp</item>
</style>
<!-- stats small item container, for placing inside StatsSmallGroupContainer -->
<style name="StatsSmallItemContainer" parent="@style/StatsLargeItemContainer">
<item name="android:paddingTop">12dp</item>
</style>
<!-- stats small label -->
<style name="StatsSmallLabel" parent="@style/TextSmall">
<item name="android:gravity">right</item>
<item name="android:layout_gravity">right</item>
<item name="android:gravity">center</item>
<item name="android:layout_gravity">center</item>
<item name="android:singleLine">false</item>
<item name="android:textStyle">bold</item>
</style>
<!-- stats small value -->
<style name="StatsSmallValue" parent="@style/TextLarge">
<item name="android:layout_gravity">bottom</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">@android:color/white</item>
</style>
@@ -197,20 +203,15 @@ limitations under the License.
</style>
<!-- stats unit -->
<style name="StatsUnit" parent="@style/TextSmall">
<item name="android:layout_gravity">bottom</item>
<item name="android:paddingLeft">4dp</item>
<item name="android:singleLine">true</item>
</style>
<!-- stats vertical spacer -->
<style name="StatsVerticalSpacer">
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
</style>
<style name="Text">
<style name="Text">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textColor">@android:color/white</item>
</style>
</style>
<!-- text choose activity -->
<style name="TextChooseActivity">
<item name="android:textSize">18sp</item>
@@ -257,7 +258,6 @@ limitations under the License.
<item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">match_parent</item>
<item name="android:orientation">horizontal</item>
<item name="android:paddingBottom">4dp</item>
<item name="android:paddingTop">4dp</item>
</style>
@@ -290,7 +290,6 @@ limitations under the License.
<style name="TrackWidgetHorizontalSeparator">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">match_parent</item>
<item name="android:orientation">horizontal</item>
</style>
<!-- track widget item container -->
<style name="TrackWidgetItemContainer">
@@ -354,7 +353,7 @@ limitations under the License.
<item name="android:layout_weight">2</item>
<item name="android:layout_width">0dp</item>
<item name="android:singleLine">true</item>
</style>
</style>
<!-- track widget stats container -->
<style name="TrackWidgetStatsContainer">
<item name="android:clickable">true</item>
@@ -369,7 +368,6 @@ limitations under the License.
<item name="android:layout_height">0dp</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">match_parent</item>
<item name="android:orientation">horizontal</item>
</style>
<!-- track widget unit -->
<style name="TrackWidgetUnit">
@@ -412,7 +410,6 @@ limitations under the License.
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginTop">-4dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:orientation">horizontal</item>
</style>
<!-- track widget value container 4x3 -->
<style name="TrackWidgetValueContainer4x3" parent="@style/TrackWidgetValueContainer">
-11
View File
@@ -33,16 +33,5 @@ limitations under the License.
android:defaultValue="false"
android:key="@string/stats_show_coordinate_key"
android:title="@string/settings_stats_coordinate" />
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/stats_show_calorie_key"
android:title="@string/stats_calorie" />
<EditTextPreference
android:dependency="@string/stats_show_calorie_key"
android:imeOptions="actionDone"
android:inputType="number"
android:key="@string/stats_weight_key"
android:persistent="false"
android:title="@string/settings_stats_calorie_weight" />
</PreferenceCategory>
</PreferenceScreen>
@@ -19,7 +19,7 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.maps.mytracks.R;
@@ -38,7 +38,7 @@ public class AggregatedStatsActivity extends AbstractMyTracksActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StatsUtils.setTripStatisticsValues(
this, this, null, getTripStatistics(), PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
this, this, null, getTripStatistics(), ActivityType.WALKING, null);
StatsUtils.setLocationValues(this, this, null, null, false);
}
@@ -30,12 +30,6 @@ public class Constants {
*/
public static final String ACCOUNT_TYPE = "com.google";
/**
* Maximum number of track points that will be loaded at one time. With
* recording frequency of 2 seconds, 20000 corresponds to 11.1 hours.
*/
public static final int MAX_LOADED_TRACK_POINTS = 20000;
/**
* Maximum number of waypoints that will be loaded at one time.
*/
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.sync.SyncUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -32,6 +33,9 @@ import android.os.AsyncTask;
*/
public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private DeleteActivity deleteActivity;
private final long[] trackIds;
private final Context context;
@@ -82,7 +86,7 @@ public class DeleteAsyncTask extends AsyncTask<Void, Integer, Boolean> {
if (isCancelled()) {
return false;
}
Thread.sleep(1000);
Thread.sleep(ONE_SECOND);
}
} catch (InterruptedException e) {
return false;
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -41,7 +42,9 @@ import android.widget.TextView;
public class SensorStateActivity extends AbstractMyTracksActivity {
private static final String TAG = SensorStateActivity.class.getName();
private static final long ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private Handler handler;
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -38,7 +39,9 @@ import android.widget.TextView;
public class TrackController {
private static final String TAG = TrackController.class.getSimpleName();
private static final int ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private final Activity activity;
private final TrackRecordingServiceConnection trackRecordingServiceConnection;
@@ -23,6 +23,8 @@ import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.fragments.ChartFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
import com.google.android.apps.mytracks.fragments.ConfirmDeleteDialogFragment;
import com.google.android.apps.mytracks.fragments.ExportDialogFragment;
import com.google.android.apps.mytracks.fragments.ExportDialogFragment.ExportCaller;
@@ -37,11 +39,15 @@ import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection
import com.google.android.apps.mytracks.settings.SettingsActivity;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.GoogleFeedbackUtils;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.TrackUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -74,7 +80,8 @@ import java.util.Date;
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class TrackDetailActivity extends AbstractSendToGoogleActivity implements ExportCaller {
public class TrackDetailActivity extends AbstractSendToGoogleActivity
implements ChooseActivityTypeCaller, ExportCaller {
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_MARKER_ID = "marker_id";
@@ -612,4 +619,23 @@ public class TrackDetailActivity extends AbstractSendToGoogleActivity implements
}
setTitle(title);
}
public void chooseActivityType(String category) {
ChooseActivityTypeDialogFragment.newInstance(category).show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
}
@Override
public void onChooseActivityTypeDone(String iconValue, boolean newWeight) {
Track track = myTracksProviderUtils.getTrack(trackId);
String category = getString(TrackIconUtils.getIconActivityType(iconValue));
TrackUtils.updateTrack(
this, track, null, category, null, myTracksProviderUtils, trackRecordingServiceConnection, newWeight);
// Add toast if cannot calculate calorie
if (CalorieUtils.getActivityType(this, category) == ActivityType.INVALID) {
String message = getString(R.string.stats_calorie_no_calculation, category);
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
}
@@ -21,17 +21,12 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment;
import com.google.android.apps.mytracks.fragments.ChooseActivityTypeDialogFragment.ChooseActivityTypeCaller;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackNameUtils;
import com.google.android.apps.mytracks.util.TrackRecordingServiceConnectionUtils;
import com.google.android.apps.mytracks.util.TrackUtils;
import com.google.android.maps.mytracks.R;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
@@ -42,7 +37,6 @@ import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
/**
@@ -70,6 +64,8 @@ public class TrackEditActivity extends AbstractMyTracksActivity
private AutoCompleteTextView activityType;
private Spinner activityTypeIcon;
private EditText description;
private boolean newWeight = false;
@Override
protected void onCreate(Bundle bundle) {
@@ -101,14 +97,14 @@ public class TrackEditActivity extends AbstractMyTracksActivity
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
activityType.setAdapter(adapter);
activityType.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
setActivityTypeIcon(TrackIconUtils.getIconValue(
TrackEditActivity.this, (String) activityType.getAdapter().getItem(position)));
}
});
activityType.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
setActivityTypeIcon(TrackIconUtils.getIconValue(
@@ -125,55 +121,28 @@ public class TrackEditActivity extends AbstractMyTracksActivity
iconValue = track.getIcon();
}
// Add a border around the icon
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.ic_track_airplane, options);
final int padding = 4;
final int width = options.outWidth + 2 * padding;
final int height = options.outHeight + 2 * padding;
activityTypeIcon = (Spinner) findViewById(R.id.track_edit_activity_type_icon);
ArrayAdapter<StringBuilder> activityTypeIconAdapter = new ArrayAdapter<StringBuilder>(
this, android.R.layout.simple_spinner_item,
new StringBuilder[] {new StringBuilder(iconValue)}) {
@Override
public View getView(int position, View convertView, android.view.ViewGroup parent) {
ImageView imageView =
convertView != null ? (ImageView) convertView : new ImageView(getContext());
Bitmap source = BitmapFactory.decodeResource(
getResources(), TrackIconUtils.getIconDrawable(getItem(position).toString()));
if (ApiAdapterFactory.getApiAdapter().isSpinnerBackgroundLight()) {
source = TrackIconUtils.invertBitmap(source);
}
imageView.setImageBitmap(source);
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
imageView.setPadding(padding, padding, padding, padding);
return imageView;
}
};
activityTypeIcon.setAdapter(activityTypeIconAdapter);
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(this, iconValue));
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
new ChooseActivityTypeDialogFragment().show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
}
return true;
}
});
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
@Override
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
new ChooseActivityTypeDialogFragment().show(getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.newInstance(activityType.getText().toString()).show(
getSupportFragmentManager(),
ChooseActivityTypeDialogFragment.CHOOSE_ACTIVITY_TYPE_DIALOG_TAG);
return true;
} else {
return false;
}
return true;
}
});
@@ -184,36 +153,9 @@ public class TrackEditActivity extends AbstractMyTracksActivity
save.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
track.setName(name.getText().toString());
String category = activityType.getText().toString();
boolean newCategory = !category.equals(track.getCategory());
boolean isRecording = trackId
== PreferencesUtils.getLong(TrackEditActivity.this, R.string.recording_track_id_key);
if (newCategory && !isRecording) {
// Update calorie
double calorie = CalorieUtils.getTrackCalorie(TrackEditActivity.this, track, -1L);
track.getTripStatistics().setCalorie(calorie);
}
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(TrackEditActivity.this, category));
track.setDescription(description.getText().toString());
track.setModifiedTime(System.currentTimeMillis());
myTracksProviderUtils.updateTrack(track);
if (newCategory && isRecording) {
// Update calorie through track recording service
TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
}
boolean driveSync = PreferencesUtils.getBoolean(
TrackEditActivity.this, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
if (driveSync) {
PreferencesUtils.addToList(TrackEditActivity.this, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
}
TrackUtils.updateTrack(TrackEditActivity.this, track, name.getText().toString(),
activityType.getText().toString(), description.getText().toString(),
myTracksProviderUtils, trackRecordingServiceConnection, newWeight);
finish();
}
});
@@ -261,26 +203,18 @@ public class TrackEditActivity extends AbstractMyTracksActivity
protected int getLayoutResId() {
return R.layout.track_edit;
}
/**
* Sets the activity type icon.
*
* @param value the icon value
*/
@SuppressWarnings("unchecked")
private void setActivityTypeIcon(String value) {
iconValue = value;
ArrayAdapter<StringBuilder> adapter =
(ArrayAdapter<StringBuilder>) activityTypeIcon.getAdapter();
StringBuilder stringBuilder = adapter.getItem(0);
stringBuilder.delete(0, stringBuilder.length());
stringBuilder.append(value);
adapter.notifyDataSetChanged();
TrackIconUtils.setIconSpinner(activityTypeIcon, value);
}
@Override
public void onChooseActivityTypeDone(String value) {
activityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
public void onChooseActivityTypeDone(String value, boolean hasNewWeight) {
if (!newWeight) {
newWeight = hasNewWeight;
}
setActivityTypeIcon(value);
activityType.setText(getString(TrackIconUtils.getIconActivityType(value)));
}
}
@@ -91,50 +91,58 @@ public class MyTracksProvider extends ContentProvider {
} else {
// Incremental upgrades. One if statement per DB version.
// Add track points SENSOR column
// Add track point SENSOR column
if (oldVersion <= 17) {
Log.w(TAG, "Upgrade DB: Adding sensor column.");
Log.w(TAG, "Upgrade DB: Adding track point sensor column.");
db.execSQL("ALTER TABLE " + TrackPointsColumns.TABLE_NAME + " ADD "
+ TrackPointsColumns.SENSOR + " BLOB");
}
// Add tracks TABLEID column
// Add track TABLEID column
if (oldVersion <= 18) {
Log.w(TAG, "Upgrade DB: Adding tableid column.");
Log.w(TAG, "Upgrade DB: Adding track tableid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.TABLEID
+ " STRING");
}
// Add tracks ICON column
// Add track ICON column
if (oldVersion <= 19) {
Log.w(TAG, "Upgrade DB: Adding icon column.");
Log.w(TAG, "Upgrade DB: Adding track icon column.");
db.execSQL(
"ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.ICON + " STRING");
}
// Add track DRIVEID, MODIFIEDTIME, and SHAREDWITHME columns
if (oldVersion <= 20) {
Log.w(TAG, "Upgrade DB: Adding driveid column.");
Log.w(TAG, "Upgrade DB: Adding track driveid column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.DRIVEID
+ " STRING");
Log.w(TAG, "Upgrade DB: Adding modifiedtime column.");
Log.w(TAG, "Upgrade DB: Adding track modifiedtime column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD "
+ TracksColumns.MODIFIEDTIME + " INTEGER");
Log.w(TAG, "Upgrade DB: Adding sharedwithme column.");
Log.w(TAG, "Upgrade DB: Adding track sharedwithme column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD "
+ TracksColumns.SHAREDWITHME + " INTEGER");
Log.w(TAG, "Upgrade DB: Adding sharedowner column.");
Log.w(TAG, "Upgrade DB: Adding track sharedowner column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.SHAREDOWNER
+ " STRING");
}
// Add waypoint CALORIE and PHOTOURL columns. Add track CALORIE column.
if (oldVersion <= 21) {
Log.w(TAG, "Upgrade DB: Adding photo url column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD " + WaypointsColumns.PHOTOURL
+ " STRING");
Log.w(TAG, "Upgrade DB: Adding waypoint calorie column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD "
+ WaypointsColumns.CALORIE + " FLOAT");
Log.w(TAG, "Upgrade DB: Adding waypoint photo url column.");
db.execSQL("ALTER TABLE " + WaypointsColumns.TABLE_NAME + " ADD "
+ WaypointsColumns.PHOTOURL + " STRING");
Log.w(TAG, "Upgrade DB: Adding track calorie column.");
db.execSQL("ALTER TABLE " + TracksColumns.TABLE_NAME + " ADD " + TracksColumns.CALORIE
+ " INTEGER");
+ " FLOAT");
}
}
}
@@ -360,7 +360,8 @@ public class SearchEngine {
}
// Score recent tracks higher.
long timeAgoHours = (query.currentTimestamp - timestamp) / (60L * 60L * 1000L);
long timeAgoHours = (long) ((query.currentTimestamp - timestamp) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN * UnitConversions.MIN_TO_HR);
if (timeAgoHours > 0L) {
return squash(timeAgoHours);
} else {
@@ -492,52 +492,60 @@ public class TrackDataHub implements DataSourceListener {
long lastTrackPointId = myTracksProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1;
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(selectedTrackId,
localLastSeenLocationId + 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
boolean includeNextPoint = false;
while (iterator.hasNext()) {
Location location = iterator.next();
long locationId = iterator.getLocationId();
LocationIterator locationIterator = null;
// Stop if past the last wanted point
if (maxPointId != -1L && locationId > maxPointId) {
break;
}
try {
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(selectedTrackId,
localLastSeenLocationId + 1, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
if (localFirstSeenLocationId == -1) {
localFirstSeenLocationId = locationId;
}
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
long locationId = locationIterator.getLocationId();
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) {
// TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSegmentSplit(location);
includeNextPoint = true;
// Stop if past the last wanted point
if (maxPointId != -1L && locationId > maxPointId) {
break;
}
} else {
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
if (localFirstSeenLocationId == -1) {
localFirstSeenLocationId = locationId;
}
if (samplingFrequency == -1) {
long numTotalPoints = Math.max(0L, lastTrackPointId - localFirstSeenLocationId);
samplingFrequency = 1 + (int) (numTotalPoints / targetNumPoints);
}
if (!LocationUtils.isValidLocation(location)) {
// TODO: also include the last valid point before a split
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
trackDataListener.onSegmentSplit(location);
includeNextPoint = true;
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
// Also include the last point if the selected track is not recording.
if (includeNextPoint || (localNumLoadedPoints % samplingFrequency == 0)
|| (locationId == lastTrackPointId && !isSelectedTrackRecording())) {
includeNextPoint = false;
for (TrackDataListener trackDataListener : sampledInListeners) {
trackDataListener.onSampledInTrackPoint(location);
}
} else {
for (TrackDataListener trackDataListener : sampledOutListeners) {
trackDataListener.onSampledOutTrackPoint(location);
}
}
}
}
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
localNumLoadedPoints++;
localLastSeenLocationId = locationId;
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
}
iterator.close();
if (updateSamplingState) {
numLoadedPoints = localNumLoadedPoints;
@@ -453,7 +453,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
if (tripStatisticsUpdater != null) {
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
TripStatistics tripStatistics = tripStatisticsUpdater.getTripStatistics();
if (chartByDistance) {
double distance = tripStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
@@ -16,12 +16,16 @@
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
@@ -30,20 +34,22 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* A DialogFragment to choose an activity type.
*
*
* @author apoorvn
*/
public class ChooseActivityTypeDialogFragment extends DialogFragment {
/**
* Interface for caller of this dialog fragment.
*
*
* @author apoorvn
*/
public interface ChooseActivityTypeCaller {
@@ -51,12 +57,28 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
/**
* Called when choose activity type is done.
*/
public void onChooseActivityTypeDone(String iconValue);
public void onChooseActivityTypeDone(String iconValue, boolean newWeight);
}
public static final String CHOOSE_ACTIVITY_TYPE_DIALOG_TAG = "chooseActivityType";
private static final String KEY_CATEGORY = "category";
private ChooseActivityTypeCaller caller;
private ChooseActivityTypeImageAdapter imageAdapter;
private AlertDialog alertDialog;
private View weightContainer;
private TextView weight;
private String weightValue;
public static ChooseActivityTypeDialogFragment newInstance(String category) {
Bundle bundle = new Bundle();
bundle.putString(KEY_CATEGORY, category);
ChooseActivityTypeDialogFragment fragment = new ChooseActivityTypeDialogFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onAttach(Activity activity) {
@@ -71,12 +93,31 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
GridView gridView =
(GridView) getActivity().getLayoutInflater().inflate(R.layout.choose_activity_type, null);
final List<String> iconValues = TrackIconUtils.getAllIconValues();
View view = getActivity().getLayoutInflater().inflate(R.layout.choose_activity_type, null);
GridView gridView = (GridView) view.findViewById(R.id.choose_activity_type_grid_view);
weightContainer = view.findViewById(R.id.choose_activity_type_weight_container);
TextView weightLabel = (TextView) view.findViewById(R.id.choose_activity_type_weight_label);
weightLabel.setText(
PreferencesUtils.isMetricUnits(getActivity()) ? R.string.description_weight_metric
: R.string.description_weight_imperial);
weight = (TextView) view.findViewById(R.id.choose_activity_type_weight);
ImageButton imageButton = (ImageButton) view.findViewById(
R.id.choose_activity_type_weight_image_button);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
double weightDefault = PreferencesUtils.WEIGHT_DEFAULT;
if (!PreferencesUtils.isMetricUnits(getActivity())) {
weightDefault *= UnitConversions.KG_TO_LB;
}
weight.setText(StringUtils.formatWeight(weightDefault));
}
});
List<Integer> imageIds = new ArrayList<Integer>();
for (String iconValue : iconValues) {
for (String iconValue : TrackIconUtils.getAllIconValues()) {
imageIds.add(TrackIconUtils.getIconDrawable(iconValue));
}
@@ -88,18 +129,77 @@ public class ChooseActivityTypeDialogFragment extends DialogFragment {
int height = options.outHeight + 2 * padding;
gridView.setColumnWidth(width);
ChooseActivityTypeImageAdapter imageAdapter =
new ChooseActivityTypeImageAdapter(getActivity(), imageIds, width, height, padding);
imageAdapter = new ChooseActivityTypeImageAdapter(
getActivity(), imageIds, width, height, padding);
gridView.setAdapter(imageAdapter);
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
dismiss();
caller.onChooseActivityTypeDone(iconValues.get(position));
alertDialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(true);
imageAdapter.setSelected(position);
imageAdapter.notifyDataSetChanged();
updateWeightContainer(position);
}
});
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setTitle(R.string.track_edit_activity_type_hint).setView(gridView).create();
alertDialog = new AlertDialog.Builder(getActivity()).setNegativeButton(
R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean newWeight = false;
if (weightContainer.getVisibility() == View.VISIBLE) {
String newValue = weight.getText().toString();
if (!newValue.equals(weightValue)) {
newWeight = true;
PreferencesUtils.storeWeightValue(getActivity(), newValue);
}
}
int selected = imageAdapter.getSelected();
caller.onChooseActivityTypeDone(
TrackIconUtils.getAllIconValues().get(selected), newWeight);
}
}).setTitle(R.string.track_edit_activity_type_hint).setView(view).create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
int position = getPosition();
alertDialog.getButton(Dialog.BUTTON_POSITIVE).setEnabled(position != -1);
if (position != -1) {
imageAdapter.setSelected(position);
imageAdapter.notifyDataSetChanged();
}
updateWeightContainer(position);
double weightDisplayValue = PreferencesUtils.getWeightDisplayValue(getActivity());
weightValue = StringUtils.formatWeight(weightDisplayValue);
weight.setText(weightValue);
}
});
return alertDialog;
}
private int getPosition() {
String category = getArguments().getString(KEY_CATEGORY);
if (category == null) {
return -1;
}
String iconValue = TrackIconUtils.getIconValue(getActivity(), category);
if (iconValue.equals("")) {
return -1;
}
List<String> iconValues = TrackIconUtils.getAllIconValues();
for (int i = 0; i < iconValues.size(); i++) {
if (iconValues.get(i).equals(iconValue)) {
return i;
}
}
return -1;
}
private void updateWeightContainer(int position) {
boolean showWeight = position == 0 || position == 1 || position == 2;
weightContainer.setVisibility(showWeight ? View.VISIBLE : View.GONE);
}
}
@@ -16,7 +16,10 @@
package com.google.android.apps.mytracks.fragments;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
@@ -36,6 +39,7 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
private final int width;
private final int height;
private final int padding;
private int selected = -1;
public ChooseActivityTypeImageAdapter(
Context context, List<Integer> imageIds, int width, int height, int padding) {
@@ -61,6 +65,14 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
return 0;
}
public int getSelected() {
return selected;
}
public void setSelected(int position) {
selected = position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
@@ -69,6 +81,11 @@ public class ChooseActivityTypeImageAdapter extends BaseAdapter {
} else {
imageView = (ImageView) convertView;
}
if (position == selected) {
imageView.setBackgroundResource(R.drawable.list_selector_background_transition_holo_dark);
} else {
imageView.setBackgroundColor(Color.TRANSPARENT);
}
imageView.setImageResource(imageIds.get(position));
imageView.setMinimumHeight(height);
imageView.setMinimumWidth(width);
@@ -22,9 +22,10 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.apps.mytracks.util.PhotoUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -128,6 +129,9 @@ public class MarkerDetailFragment extends Fragment {
int visibility = waypointInfo.getVisibility() == View.GONE ? View.VISIBLE : View.GONE;
textGradient.setVisibility(visibility);
waypointInfo.setVisibility(visibility);
if (visibility == View.VISIBLE) {
handler.postDelayed(hideText, HIDE_TEXT_DELAY);
}
}
});
textGradient = (ImageView) view.findViewById(R.id.marker_detail_waypoint_text_gradient);
@@ -277,8 +281,12 @@ public class MarkerDetailFragment extends Fragment {
TextView name = (TextView) getView().findViewById(R.id.marker_detail_statistics_name);
setTextView(name, waypoint.getName());
StatsUtils.setTripStatisticsValues(getActivity(), null, getView(),
waypoint.getTripStatistics(), PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
Track track = myTracksProviderUtils.getTrack(waypoint.getTrackId());
ActivityType activityType = track != null ? CalorieUtils.getActivityType(
getActivity(), track.getCategory())
: ActivityType.INVALID;
StatsUtils.setTripStatisticsValues(
getActivity(), null, getView(), waypoint.getTripStatistics(), activityType, null);
StatsUtils.setLocationValues(getActivity(), null, getView(), waypoint.getLocation(), false);
}
}
@@ -23,9 +23,13 @@ import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TrackDataType;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StatsUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.location.Location;
@@ -33,9 +37,12 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Spinner;
import java.util.EnumSet;
@@ -49,13 +56,15 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public static final String STATS_FRAGMENT_TAG = "statsFragment";
private static final int ONE_SECOND = 1000;
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private TrackDataHub trackDataHub;
private Handler handler;
private Location lastLocation = null;
private TripStatistics lastTripStatistics = null;
private String category = "";
private int recordingGpsAccuracy = PreferencesUtils.RECORDING_GPS_ACCURACY_DEFAULT;
// A runnable to update the total time field.
@@ -81,13 +90,34 @@ public class StatsFragment extends Fragment implements TrackDataListener {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
handler = new Handler();
updateUi(getActivity());
Spinner activityTypeIcon = (Spinner) getView().findViewById(R.id.stats_activity_type_icon);
activityTypeIcon.setAdapter(TrackIconUtils.getIconSpinnerAdapter(getActivity(), ""));
activityTypeIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
activityTypeIcon.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
((TrackDetailActivity) getActivity()).chooseActivityType(category);
}
return true;
}
});
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
updateUi(getActivity());
if (isSelectedTrackRecording()) {
handler.post(updateTotalTime);
}
@@ -106,8 +136,9 @@ public class StatsFragment extends Fragment implements TrackDataListener {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (isResumed()) {
if (isResumed()) {
lastTripStatistics = track != null ? track.getTripStatistics() : null;
category = track != null ? track.getCategory() : "";
updateUi(getActivity());
}
}
@@ -260,9 +291,10 @@ public class StatsFragment extends Fragment implements TrackDataListener {
* Updates the UI.
*/
private void updateUi(FragmentActivity activity) {
StatsUtils.setTripStatisticsValues(activity, activity, null, lastTripStatistics,
trackDataHub == null ? PreferencesUtils.RECORDING_TRACK_ID_DEFAULT
: trackDataHub.getSelectedTrackId());
ActivityType activityType = CalorieUtils.getActivityType(activity, category);
String trackIconValue = TrackIconUtils.getIconValue(activity, category);
StatsUtils.setTripStatisticsValues(
activity, activity, null, lastTripStatistics, activityType, trackIconValue);
StatsUtils.setLocationValues(
activity, activity, null, lastLocation, isSelectedTrackRecording());
}
@@ -59,6 +59,7 @@ public class PreferenceBackupHelper {
doNotBackup.add(context.getString(R.string.google_account_key));
doNotBackup.add(context.getString(R.string.recording_track_id_key));
doNotBackup.add(context.getString(R.string.recording_track_paused_key));
doNotBackup.add(context.getString(R.string.weight_key));
}
/**
@@ -134,16 +134,18 @@ public class FileTrackExporter implements TrackExporter {
boolean wroteSegment = false;
boolean isLastLocationValid = false;
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
int locationNumber = 0;
LocationIterator locationIterator = null;
try {
int locationNumber = 0;
while (iterator.hasNext()) {
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
while (locationIterator.hasNext()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Location location = iterator.next();
Location location = locationIterator.next();
setLocationTime(location, offset);
locationNumber++;
@@ -195,7 +197,9 @@ public class FileTrackExporter implements TrackExporter {
trackWriter.writeEndTrack(track, null);
}
} finally {
iterator.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackFileFormat;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
@@ -172,7 +173,8 @@ public class TcxTrackWriter implements TrackWriter {
if (printWriter != null) {
String startTime = StringUtils.formatDateTimeIso8601(
track.getTripStatistics().getStartTime());
long totalTimeInSeconds = track.getTripStatistics().getTotalTime() / 1000;
long totalTimeInSeconds = (long) (track.getTripStatistics().getTotalTime()
* UnitConversions.MS_TO_S);
printWriter.println("<Activities>");
printWriter.println("<Activity Sport=\"" + sportType.getName() + "\">");
@@ -18,27 +18,30 @@ package com.google.android.apps.mytracks.io.file.importer;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.services.TrackRecordingService;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.apps.mytracks.util.CalorieUtils;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -105,6 +108,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
private final long importTrackId;
private final MyTracksProviderUtils myTracksProviderUtils;
private final int recordingDistanceInterval;
private final double weight;
private final List<Long> trackIds;
private final List<Waypoint> waypoints;
@@ -142,6 +146,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
this.recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
this.weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
trackIds = new ArrayList<Long>();
waypoints = new ArrayList<Waypoint>();
}
@@ -209,20 +215,20 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (track == null) {
return;
}
Cursor trackPointCursor = null;
int waypointPosition = -1;
Waypoint waypoint = null;
Location location = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
LocationIterator locationIterator = null;
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
try {
trackPointCursor = myTracksProviderUtils.getTrackPointCursor(track.getId(), -1L, -1, false);
if (trackPointCursor == null) {
return;
}
int waypointPosition = -1;
Waypoint waypoint = null;
int trackPointPosition = -1;
Location trackPoint = null;
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
while (true) {
if (waypoint == null) {
@@ -233,28 +239,25 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
return;
}
}
if (trackPoint == null) {
trackPointPosition++;
trackPoint = trackPointCursor.moveToPosition(trackPointPosition) ? myTracksProviderUtils
.createTrackPoint(trackPointCursor)
: null;
if (trackPoint == null) {
if (location == null) {
if (!locationIterator.hasNext()) {
// No more track points. Ignore the rest of the waypoints.
return;
}
trackTripStatisticstrackUpdater.addLocation(trackPoint, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
markerTripStatisticsUpdater.addLocation(trackPoint, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
location = locationIterator.next();
trackTripStatisticstrackUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
}
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
trackPoint = null;
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
if (waypoint.getLocation().getTime() > location.getTime()) {
location = null;
} else if (waypoint.getLocation().getTime() < location.getTime()) {
waypoint = null;
} else {
// The waypoint location time matches the track point time
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude()
&& trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
if (location.getLatitude() == waypoint.getLocation().getLatitude()
&& location.getLongitude() == waypoint.getLocation().getLongitude()) {
// Get tripStatistics, description, and icon
TripStatistics tripStatistics;
@@ -262,7 +265,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
String icon;
if (waypoint.getType() == WaypointType.STATISTICS) {
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
markerTripStatisticsUpdater = new TripStatisticsUpdater(trackPoint.getTime());
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypointDescription = new DescriptionGeneratorImpl(context)
.generateWaypointDescription(tripStatistics);
icon = context.getString(R.string.marker_statistics_icon_url);
@@ -279,15 +282,15 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// Insert waypoint
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
-1L, -1L, trackPoint, tripStatistics, waypoint.getPhotoUrl());
-1L, -1L, location, tripStatistics, waypoint.getPhotoUrl());
myTracksProviderUtils.insertWaypoint(newWaypoint);
}
waypoint = null;
}
}
} finally {
if (trackPointCursor != null) {
trackPointCursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -412,10 +415,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* last two points. GPS points tend to have some inherent imprecision,
* speed and bearing will likely be off, so the statistics for things
* like max speed will also be off.
*/
float speed = trackData.lastLocationInCurrentSegment.distanceTo(location) * 1000.0f
/ timeDifference;
location.setSpeed(speed);
*/
double duration = timeDifference * UnitConversions.MS_TO_S;
double speed = trackData.lastLocationInCurrentSegment.distanceTo(location) / duration;
location.setSpeed((float) speed);
}
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
}
@@ -469,8 +472,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (importTrackId == -1L) {
return null;
}
return FileUtils.getPath(
String path = FileUtils.getPath(
FileUtils.PICTURES_DIR, Long.toString(importTrackId), fileName);
return Uri.fromFile(new File(path)).toString();
}
/**
@@ -486,8 +490,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
latitudeValue = Double.parseDouble(latitude);
longitudeValue = Double.parseDouble(longitude);
} catch (NumberFormatException e) {
throw new SAXException(createErrorMessage(
String.format("Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
throw new SAXException(createErrorMessage(String.format(
Locale.US, "Unable to parse latitude longitude: %s %s", latitude, longitude)), e);
}
Double altitudeValue = null;
if (altitude != null) {
@@ -495,7 +499,8 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
altitudeValue = Double.parseDouble(altitude);
} catch (NumberFormatException e) {
throw new SAXException(
createErrorMessage(String.format("Unable to parse altitude: %s", altitude)), e);
createErrorMessage(String.format(Locale.US, "Unable to parse altitude: %s", altitude)),
e);
}
}
@@ -507,7 +512,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
timeValue = StringUtils.getTime(time);
} catch (IllegalArgumentException e) {
throw new SAXException(
createErrorMessage(String.format("Unable to parse time: %s", time)), e);
createErrorMessage(String.format(Locale.US, "Unable to parse time: %s", time)), e);
}
}
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
@@ -548,8 +553,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
location.getTime() != -1L ? location.getTime() : trackData.importTime);
}
trackData.tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
ActivityType activityType = CalorieUtils.getActivityType(context, category);
trackData.tripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
trackData.numBufferedLocations++;
@@ -156,6 +156,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
name = null;
description = null;
category = null;
photoUrl = null;
latitude = attributes.getValue(ATTRIBUTE_LAT);
longitude = attributes.getValue(ATTRIBUTE_LON);
altitude = null;
@@ -91,6 +91,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
public void startElement(String uri, String localName, String tag, Attributes attributes)
throws SAXException {
if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
/*
* Note that a track is contained in a Placemark, calling onWaypointStart
* will clear various track variables like name, category, and
* description.
*/
onWaypointStart();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
trackStarted = true;
@@ -110,6 +115,10 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
if (tag.equals(TAG_KML)) {
onFileEnd();
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) {
/*
* Note that a track is contained in a Placemark, calling onWaypointend is
* save since waypointType is not set for a track.
*/
onWaypointEnd();
} else if (localName.equals(TAG_COORDINATES)) {
onWaypointLocationEnd();
@@ -159,12 +168,12 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
name = null;
description = null;
category = null;
photoUrl = null;
latitude = null;
longitude = null;
altitude = null;
time = null;
waypointType = null;
photoUrl = null;
}
/**
@@ -6,6 +6,7 @@ import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
@@ -227,23 +228,19 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
*/
private boolean uploadAllTrackPoints(Fusiontables fusiontables, String tableId, Track track)
throws IOException {
Cursor cursor = null;
int numberOfPoints = track.getNumberOfPoints();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
int readCount = 0;
LocationIterator locationIterator = null;
try {
cursor = myTracksProviderUtils.getTrackPointCursor(trackId, -1L, -1, false);
if (cursor == null) {
Log.d(TAG, "Location cursor is null");
return false;
}
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
trackId, -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
int count = cursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
Location location = myTracksProviderUtils.createTrackPoint(cursor);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
locations.add(location);
if (LocationUtils.isValidLocation(location)) {
@@ -258,13 +255,13 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
}
// Upload periodically
int readCount = i + 1;
readCount++;
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
if (!prepareAndUploadPoints(fusiontables, tableId, track, locations, false)) {
Log.d(TAG, "Unable to upload points");
return false;
}
updateProgress(readCount, count);
updateProgress(readCount, numberOfPoints);
locations.clear();
}
}
@@ -285,8 +282,8 @@ public class SendFusionTablesAsyncTask extends AbstractSendAsyncTask {
}
return true;
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -19,6 +19,7 @@ import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DescriptionGenerator;
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
@@ -235,33 +236,29 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
*/
@VisibleForTesting
boolean uploadAllTrackPoints(Track track) {
Cursor cursor = null;
int numberOfPoints = track.getNumberOfPoints();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
// For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (numberOfPoints / 250.0));
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
int readCount = 0;
LocationIterator locationIterator = null;
try {
cursor = myTracksProviderUtils.getTrackPointCursor(trackId, -1L, -1, false);
if (cursor == null) {
Log.d(TAG, "Location cursor is null");
return false;
}
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
trackId, -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
int count = cursor.getCount();
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
Location lastValidLocation = null;
boolean sentStartMarker = false;
// For chart server, limit the number of elevation readings to 250.
int elevationSamplingFrequency = Math.max(1, (int) (count / 250.0));
Vector<Double> distances = new Vector<Double>();
Vector<Double> elevations = new Vector<Double>();
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
for (int i = 0; i < count; i++) {
cursor.moveToPosition(i);
Location location = myTracksProviderUtils.createTrackPoint(cursor);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
locations.add(location);
if (LocationUtils.isValidLocation(location)) {
@@ -279,20 +276,20 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
}
tripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (i % elevationSamplingFrequency == 0) {
ActivityType.INVALID, PreferencesUtils.WEIGHT_DEFAULT);
if (readCount % elevationSamplingFrequency == 0) {
distances.add(tripStatisticsUpdater.getTripStatistics().getTotalDistance());
elevations.add(tripStatisticsUpdater.getSmoothedElevation());
}
// Upload periodically
int readCount = i + 1;
readCount++;
if (readCount % MAX_POINTS_PER_UPLOAD == 0) {
if (!prepareAndUploadPoints(track, locations, false)) {
Log.d(TAG, "Unable to upload points");
return false;
}
updateProgress(readCount, count);
updateProgress(readCount, numberOfPoints);
locations.clear();
}
}
@@ -318,8 +315,8 @@ public class SendMapsAsyncTask extends AbstractSendAsyncTask {
}
return true;
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
}
@@ -23,6 +23,7 @@ import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
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;
@@ -44,6 +45,7 @@ import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.TrackIconUtils;
import com.google.android.apps.mytracks.util.TrackNameUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
@@ -59,7 +61,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.location.Location;
import android.location.LocationManager;
@@ -101,9 +102,14 @@ public class TrackRecordingService extends Service {
public static final double MAX_NO_MOVEMENT_SPEED = 0.224;
private static final String TAG = TrackRecordingService.class.getSimpleName();
private static final long ONE_SECOND = 1000; // in milliseconds
private static final long ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
// 1 minute in milliseconds
private static final long ONE_MINUTE = (long) (UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
@VisibleForTesting
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
@@ -126,7 +132,8 @@ public class TrackRecordingService extends Service {
private int recordingGpsAccuracy;
private int autoResumeTrackTimeout;
private long currentRecordingInterval;
private double weight;
// The following variables are set when recording:
private TripStatisticsUpdater trackTripStatisticsUpdater;
private TripStatisticsUpdater markerTripStatisticsUpdater;
@@ -224,6 +231,10 @@ public class TrackRecordingService extends Service {
R.string.auto_resume_track_timeout_key,
PreferencesUtils.AUTO_RESUME_TRACK_TIMEOUT_DEFAULT);
}
if (key == null || key.equals(PreferencesUtils.getKey(context, R.string.weight_key))) {
weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
}
}
};
@@ -650,31 +661,27 @@ public class TrackRecordingService extends Service {
}
markerTripStatisticsUpdater = new TripStatisticsUpdater(markerStartTime);
Cursor cursor = null;
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
LocationIterator locationIterator = null;
try {
// TODO: how to handle very long track.
cursor = myTracksProviderUtils.getTrackPointCursor(
recordingTrackId, -1L, Constants.MAX_LOADED_TRACK_POINTS, true);
if (cursor == null) {
Log.e(TAG, "Cursor is null.");
} else {
if (cursor.moveToLast()) {
do {
Location location = myTracksProviderUtils.createTrackPoint(cursor);
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (location.getTime() > markerStartTime) {
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
}
} while (cursor.moveToPrevious());
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
if (location.getTime() > markerStartTime) {
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
}
}
} catch (RuntimeException e) {
Log.e(TAG, "RuntimeException", e);
} finally {
if (cursor != null) {
cursor.close();
if (locationIterator != null) {
locationIterator.close();
}
}
startRecording(true);
@@ -1010,11 +1017,11 @@ public class TrackRecordingService extends Service {
try {
Uri uri = myTracksProviderUtils.insertTrackPoint(location, track.getId());
long trackPointId = Long.parseLong(uri.getLastPathSegment());
trackTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, true,
CalorieUtils.getActivityType(context, track.getCategory()), PreferencesUtils.getInt(context,
R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT));
markerTripStatisticsUpdater.addLocation(location, recordingDistanceInterval, false,
ActivityType.INVALID, PreferencesUtils.STATS_WEIGHT_DEFAULT);
ActivityType activityType = CalorieUtils.getActivityType(context, track.getCategory());
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
updateRecordingTrack(track, trackPointId, LocationUtils.isValidLocation(location));
} catch (SQLiteException e) {
/*
@@ -1377,23 +1384,14 @@ public class TrackRecordingService extends Service {
return;
}
double calorie = CalorieUtils.getTrackCalorie(context, track, -1L);
// Update to database
track.getTripStatistics().setCalorie(calorie);
myTracksProviderUtils.updateTrack(track);
double[] calories = CalorieUtils.updateTrackCalorie(context, track);
// Update track statistics
trackTripStatisticsUpdater.updateCalorie(calorie);
trackTripStatisticsUpdater.updateCalorie(calories[0]);
// Update marker statistics
Waypoint wayPoint = myTracksProviderUtils.getLastWaypoint(
recordingTrackId, WaypointType.STATISTICS);
long trackPointId = wayPoint != null ? myTracksProviderUtils.getTrackPointId(
recordingTrackId, wayPoint.getLocation())
: -1L;
calorie = CalorieUtils.getTrackCalorie(context, track, trackPointId);
markerTripStatisticsUpdater.updateCalorie(calorie);
// Update marker statistics
markerTripStatisticsUpdater.updateCalorie(calories[1]);
}
});
}
@@ -19,6 +19,7 @@ package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Sensor.SensorState;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.bluetooth.BluetoothAdapter;
@@ -39,6 +40,8 @@ import java.util.ArrayList;
*/
public class BluetoothSensorManager extends SensorManager {
// 1 second in milliseconds
private static final long ONE_SECOND = (long) UnitConversions.S_TO_MS;
private static final BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter();
private static final String TAG = BluetoothConnectionManager.class.getSimpleName();
@@ -69,7 +72,7 @@ public class BluetoothSensorManager extends SensorManager {
while (adapters.isEmpty()) {
synchronized (mutex) {
try {
mutex.wait(1000L);
mutex.wait(ONE_SECOND);
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for default bluetooth adapter", e);
}
@@ -38,6 +38,7 @@ import android.util.Log;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.util.Locale;
/**
* Ant Sensor Manager.
@@ -396,7 +397,8 @@ public class AntSensorManager extends SensorManager {
if (channelConfig[channel].isInitializing()) {
if (message[AntMesg.MESG_DATA_OFFSET + 2] != 0) {
// Error response
Log.e(TAG, String.format("Error code(%#02x) on message ID(%#02x) on channel %d",
Log.e(TAG, String.format(Locale.US,
"Error code(%#02x) on message ID(%#02x) on channel %d",
message[AntMesg.MESG_DATA_OFFSET + 2], message[AntMesg.MESG_DATA_OFFSET + 1],
channel));
} else {
@@ -50,8 +50,7 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
static final float TTS_SPEECH_RATE = 0.9f;
private static final String TAG = AnnouncementPeriodicTask.class.getSimpleName();
private static final long HOUR_TO_MILLISECOND = 60 * 60 * 1000;
@VisibleForTesting
static final HashMap<String, String> SPEECH_PARAMS = new HashMap<String, String>();
static {
@@ -249,7 +248,9 @@ public class AnnouncementPeriodicTask implements PeriodicTask {
} else {
speed = speed == 0 ? 0.0 : 1 / speed;
int paceId = metricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
rate = context.getString(paceId, getAnnounceTime((long) (speed * HOUR_TO_MILLISECOND)));
long time = (long) (speed * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
rate = context.getString(paceId, getAnnounceTime(time));
}
int totalDistanceId = metricUnits ? R.plurals.voiceTotalDistanceKilometers
@@ -17,22 +17,12 @@
package com.google.android.apps.mytracks.settings;
import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.text.InputType;
import android.util.Log;
import android.widget.EditText;
/**
* An activity for accessing stats settings.
@@ -41,25 +31,12 @@ import android.widget.EditText;
*/
public class StatsSettingsActivity extends AbstractSettingsActivity {
private static final String TAG = MapSettingsActivity.class.getSimpleName();
private CheckBoxPreference caloriePreference;
private EditTextPreference weightPreference;
private static final int WEIGHT_INPUT_DIALOG = 1;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
addPreferencesFromResource(R.xml.stats_settings);
caloriePreference = (CheckBoxPreference) findPreference(getString(R.string.stats_show_calorie_key));
weightPreference = (EditTextPreference) findPreference(getString(R.string.stats_weight_key));
configCaloriePreference();
configWeightPreference();
updateWeightSummary();
/*
* Note configureUnitsListPreference will trigger
* configureRateListPreference
@@ -72,27 +49,19 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
*/
private void configUnitsListPreference() {
@SuppressWarnings("deprecation")
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_units_key));
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_units_key));
OnPreferenceChangeListener listener = new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
configRateListPreference(PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue));
/*
* It is necessary to change the weight summary as they are in the same
* activity.
*/
updateWeightSummary(PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue));
// Recreate weight value dialog.
removeDialog(WEIGHT_INPUT_DIALOG);
onCreateDialog(WEIGHT_INPUT_DIALOG);
@Override
public boolean onPreferenceChange(Preference pref, Object newValue) {
boolean metricUnits = PreferencesUtils.STATS_UNITS_DEFAULT.equals((String) newValue);
configRateListPreference(metricUnits);
return true;
}
};
String value = PreferencesUtils.getString(this, R.string.stats_units_key,
PreferencesUtils.STATS_UNITS_DEFAULT);
String value = PreferencesUtils.getString(
this, R.string.stats_units_key, PreferencesUtils.STATS_UNITS_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_units_values);
String[] options = getResources().getStringArray(R.array.stats_units_options);
configureListPreference(listPreference, options, options, values, value, listener);
@@ -105,139 +74,13 @@ public class StatsSettingsActivity extends AbstractSettingsActivity {
*/
private void configRateListPreference(boolean metricUnits) {
@SuppressWarnings("deprecation")
ListPreference listPreference = (ListPreference) findPreference(getString(R.string.stats_rate_key));
String value = PreferencesUtils.getString(this, R.string.stats_rate_key,
PreferencesUtils.STATS_RATE_DEFAULT);
ListPreference listPreference = (ListPreference) findPreference(
getString(R.string.stats_rate_key));
String value = PreferencesUtils.getString(
this, R.string.stats_rate_key, PreferencesUtils.STATS_RATE_DEFAULT);
String[] values = getResources().getStringArray(R.array.stats_rate_values);
String[] options = getResources().getStringArray(
metricUnits ? R.array.stats_rate_metric_options : R.array.stats_rate_imperial_options);
configureListPreference(listPreference, options, options, values, value, null);
}
/**
* Configures the calorie preference.
*/
private void configCaloriePreference() {
caloriePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean value = (Boolean) newValue;
if (value) {
showDialog(WEIGHT_INPUT_DIALOG);
}
return true;
}
});
}
@Override
@Deprecated
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch (id) {
case WEIGHT_INPUT_DIALOG:
final EditText weightInput = new EditText(this);
weightInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
weightInput.setText(Integer.toString(getWeightDisplayValue(PreferencesUtils
.isMetricUnits(getApplicationContext()))));
weightInput.setSelectAllOnFocus(true);
dialog = (new AlertDialog.Builder(this))
.setMessage(R.string.settings_stats_calorie_weight_description)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int number) {
storeWeightValue(weightInput.getText().toString());
updateWeightSummary();
dialogInterface.cancel();
}
}).setTitle(R.string.settings_stats_calorie_weight).setView(weightInput).create();
break;
default:
break;
}
return dialog;
}
/**
* Configures the weight preference.
*/
private void configWeightPreference() {
weightPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
storeWeightValue((String) newValue);
updateWeightSummary();
return true;
}
});
weightPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference pref) {
int value = getWeightDisplayValue(PreferencesUtils.isMetricUnits(getApplicationContext()));
((EditTextPreference) pref).getEditText().setText(String.valueOf(value));
return true;
}
});
}
/**
* Updates the weight summary.
*/
private void updateWeightSummary() {
boolean metricUnits = PreferencesUtils.isMetricUnits(this);
updateWeightSummary(metricUnits);
}
/**
* Updates the weight summary.
*
* @param metricUnits true if metric units
*/
private void updateWeightSummary(boolean metricUnits) {
int displayValue = getWeightDisplayValue(metricUnits);
weightPreference.setSummary(getString(metricUnits ? R.string.value_integer_kilogram
: R.string.value_integer_pound, displayValue));
}
/**
* Gets the weight display value in metric or imperial depending on the
* preferred units.
*
* @param metricUnits true if metric units
*/
private int getWeightDisplayValue(boolean metricUnits) {
int value = PreferencesUtils.getInt(this, R.string.stats_weight_key,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
if (!metricUnits) {
value = (int) Math.round(value * UnitConversions.KG_TO_LB);
}
return value;
}
/**
* Stores the weight value, always in metric units.
*
* @param displayValue the display value
*/
private void storeWeightValue(String displayValue) {
/*
* TODO add a method to an abstract class or an utility class to avoid
* duplicating store preference logic in MapSettingsActivity.java.
*/
int value;
try {
value = Integer.parseInt(displayValue);
if (!PreferencesUtils.isMetricUnits(this)) {
value = (int) (value * UnitConversions.LB_TO_KG);
}
} catch (NumberFormatException e) {
Log.e(TAG, "invalid value " + displayValue);
value = PreferencesUtils.STATS_WEIGHT_DEFAULT;
}
PreferencesUtils.setInt(this, R.string.stats_weight_key, value);
}
}
@@ -131,7 +131,7 @@ public class TripStatisticsUpdater {
* calorie
*/
public void addLocation(Location location, int minRecordingDistance,
boolean calculateCalorie, ActivityType activityType, int weight) {
boolean calculateCalorie, ActivityType activityType, double weight) {
// Always update time
updateTime(location.getTime());
if (!LocationUtils.isValidLocation(location)) {
@@ -18,11 +18,13 @@ package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
/**
@@ -36,7 +38,7 @@ public class CalorieUtils {
* Activity types.
*/
public enum ActivityType {
CYCLING, FOOT, INVALID
CYCLING, RUNNING, WALKING, INVALID
}
private CalorieUtils() {}
@@ -47,15 +49,10 @@ public class CalorieUtils {
*/
private static final double RESTING_VO2 = 3.5;
/**
* Ratio to convert liter to kcal.
*/
private static final double L_TO_KCAL = 5.0;
/**
* Standard gravity in meters per second squared (m/s^2).
*/
private static final double EARTH_GRAVITY = 9.80665;
private static final double EARTH_GRAVITY = 9.81;
/**
* Lumped constant for all frictional losses (tires, bearings, chain).
@@ -63,22 +60,10 @@ public class CalorieUtils {
private static final double K1 = 0.0053;
/**
* Lumped constant for aerodynamic drag.
* Lumped constant for aerodynamic drag (kg/m)
*/
private static final double K2 = 0.185;
/**
* Critical speed running in meters per minute. Converts 4.5 miles per hour to
* meters per minute. 4 miles per hour is regarded as the max speed of walking
* and 5 miles per hour is regarded as the minimal speed of running, so we use
* 4.5 miles per hour as the critical speed. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
@VisibleForTesting
static final double CRTICAL_SPEED_RUNNING = 4.5 * UnitConversions.MI_TO_KM
* UnitConversions.KM_TO_M / UnitConversions.HR_TO_MIN;
/**
* Gets the activity type.
*
@@ -86,9 +71,13 @@ public class CalorieUtils {
* @param activityType the activity type
*/
public static ActivityType getActivityType(Context context, String activityType) {
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)
|| TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.FOOT;
if (activityType == null || activityType.equals("")) {
return ActivityType.INVALID;
}
if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.WALK)) {
return ActivityType.WALKING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.RUN)) {
return ActivityType.RUNNING;
} else if (TrackIconUtils.getIconValue(context, activityType).equals(TrackIconUtils.BIKE)) {
return ActivityType.CYCLING;
}
@@ -96,34 +85,79 @@ public class CalorieUtils {
}
/**
* Calculates the track calorie in kcal.
* Updates calories for a track and its waypoints.
*
* @param context the context
* @param track the track
* @param startTrackPointId the starting track point id. -1L to calculate the
* entire track
* @return an array of two doubles, first is the track calorie, second is the
* last statistics waypoint calorie
*/
public static double getTrackCalorie(Context context, Track track, long startTrackPointId) {
public static double[] updateTrackCalorie(Context context, Track track) {
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
ActivityType activityType = getActivityType(context, track.getCategory());
if (activityType == ActivityType.INVALID) {
return 0.0;
clearCalorie(myTracksProviderUtils, track);
return new double[] { 0.0, 0.0 };
}
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), startTrackPointId, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
TripStatisticsUpdater tripStatisticsUpdater = new TripStatisticsUpdater(
TripStatisticsUpdater trackTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
track.getTripStatistics().getStartTime());
int recordingDistanceInterval = PreferencesUtils.getInt(context,
R.string.recording_distance_interval_key,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT);
int statsWeight = PreferencesUtils.getInt(
context, R.string.stats_weight_key, PreferencesUtils.STATS_WEIGHT_DEFAULT);
while (iterator.hasNext()) {
tripStatisticsUpdater.addLocation(
iterator.next(), recordingDistanceInterval, true, activityType, statsWeight);
double weight = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
LocationIterator locationIterator = null;
Cursor cursor = null;
try {
Waypoint waypoint = null;
locationIterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, MyTracksProviderUtils.DEFAULT_LOCATION_FACTORY);
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
waypoint = getNextStatisticsWaypoint(myTracksProviderUtils, cursor);
}
while (locationIterator.hasNext()) {
Location location = locationIterator.next();
trackTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
markerTripStatisticsUpdater.addLocation(
location, recordingDistanceInterval, true, activityType, weight);
if (waypoint != null && waypoint.getLocation().getTime() == location.getTime()
&& waypoint.getLocation().getLatitude() == location.getLatitude()
&& waypoint.getLocation().getLongitude() == location.getLongitude()) {
waypoint.getTripStatistics()
.setCalorie(markerTripStatisticsUpdater.getTripStatistics().getCalorie());
myTracksProviderUtils.updateWaypoint(waypoint);
markerTripStatisticsUpdater = new TripStatisticsUpdater(location.getTime());
waypoint = getNextStatisticsWaypoint(myTracksProviderUtils, cursor);
}
}
} finally {
if (locationIterator != null) {
locationIterator.close();
}
if (cursor != null) {
cursor.close();
}
}
return tripStatisticsUpdater.getTripStatistics().getCalorie();
double trackCalorie = trackTripStatisticsUpdater.getTripStatistics().getCalorie();
track.getTripStatistics().setCalorie(trackCalorie);
myTracksProviderUtils.updateTrack(track);
return new double[] {
trackCalorie, markerTripStatisticsUpdater.getTripStatistics().getCalorie() };
}
/**
@@ -137,141 +171,137 @@ public class CalorieUtils {
* @param activityType the activity type
*/
public static double getCalorie(
Location start, Location stop, double grade, int weight, ActivityType activityType) {
Location start, Location stop, double grade, double weight, ActivityType activityType) {
if (activityType == ActivityType.INVALID) {
return 0.0;
}
return ActivityType.CYCLING == activityType ? getCyclingCalorie(start, stop, grade, weight)
: getFootCalorie(start, stop, grade, weight);
}
/**
* Gets the cycling calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight in kilogram of the rider plus bike
*/
@VisibleForTesting
static double getCyclingCalorie(Location start, Location stop, double grade, int weight) {
// Gets duration in seconds
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S;
// Get speed in meters per second
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
if (grade < 0) {
grade = 0.0;
}
return getCyclingCalorie(speed, grade, weight, duration);
}
/**
* Gets the cycling calorie in kcal using the following equation: <br>
* P = g * m * Vg * (K1 + s) + K2 * (Va)^2 * Vg
* <ul>
* <li>P - Power in watt (Joule/second)</li>
* <li>g - gravity, using 9.80665 meter/second^2</li>
* <li>m - mass of the rider plus bike in kilogram</li>
* <li>s - surface grade</li>
* <li>Vg - ground relative speed in meter/second</li>
* <li>Va - air relative speed. We assume the air speed is zero, so Va is
* equal to Vg.</li>
* <li>K1 - a constant which represents frictional losses, using 0.0053.</li>
* <li>K2 - a constant representing aerodynamic drag, using 0.185.</li>
* <li></li>
* </ul>
*
* @param speed the speed in meters per second
* @param grade the grade
* @param weight weight in kilogram of the rider plus bike
* @param duration the duration in seconds
*/
@VisibleForTesting
static double getCyclingCalorie(double speed, double grade, int weight, double duration) {
// Get the power in watt (Joule/second)
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// Get the calories in kcal
return power * duration * UnitConversions.J_TO_KCAL;
}
/**
* Gets the foot calorie in kcal between two locations.
*
* @param start the start location
* @param stop the stop location
* @param grade the grade
* @param weight the weight of the user in kilogram
*/
@VisibleForTesting
static double getFootCalorie(Location start, Location stop, double grade, int weight) {
// Get speed in meters per second
double averageSpeed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Get VO2 in mL/kg/min
double vo2 = getFootVo2(averageSpeed, grade);
// Duration in minutes
// Speed in m/s
double speed = (start.getSpeed() + stop.getSpeed()) / 2.0;
// Duration in min
double duration = (double) (stop.getTime() - start.getTime()) * UnitConversions.MS_TO_S
* UnitConversions.S_TO_MIN;
/*
* Get the calorie. The unit of calorie is kcal which is came from
* (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * L_TO_KCAL;
}
/**
* Gets the foot VO2 value in ml/kg/min.
*
* @param speed the speed in meters per second
* @param grade the grade
*/
@VisibleForTesting
static double getFootVo2(double speed, double grade) {
if (grade < 0) {
grade = 0.0;
if (activityType == ActivityType.CYCLING) {
/*
* Power in watt (Joule/second). See
* http://en.wikipedia.org/wiki/Bicycle_performance.
*/
double power = EARTH_GRAVITY * weight * speed * (K1 + grade) + K2 * (speed * speed * speed);
// WorkRate in kgm/min
double workRate = power * UnitConversions.W_TO_KGM;
/*
* VO2 in kgm/min/kg 1.8 = oxygen cost of producing 1 kgm/min of power
* output. 7 = oxygen cost of unloaded cycling plus resting oxygen
* consumption
*/
double vo2 = (1.8 * workRate / weight) + 7;
// Calorie in kcal
return vo2 * duration * weight * UnitConversions.KGM_TO_KCAL;
} else {
double vo2 = activityType == ActivityType.RUNNING ? getRunningVo2(speed, grade)
: getWalkingVo2(speed, grade);
/*
* Calorie in kcal (mL/kg/min * min * kg * L/mL * kcal/L)
*/
return vo2 * duration * weight * UnitConversions.ML_TO_L * UnitConversions.L_TO_KCAL;
}
return speed > CRTICAL_SPEED_RUNNING ? getHighSpeedFootVo2(speed, grade)
: getLowSpeedFootVo2(speed, grade);
}
/**
* Gets the high speed foot VO2 in ml/kg/min. This equation is appropriate for
* speeds greater than 4.5 mph like running.
* Clears calorie in the track and its waypoints.
*
* @param speed the speed in meters per second
* @param grade the grade
* @param myTracksProviderUtils the my tracks provider utils
* @param track the track
*/
@VisibleForTesting
static double getHighSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.2 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while running (horizontally). 0.9 means oxygen cost per meter of
* moving total body mass against gravity (vertically).
*/
return 0.2 * speed + 0.9 * speed * Math.abs(grade) + RESTING_VO2;
private static void clearCalorie(MyTracksProviderUtils myTracksProviderUtils, Track track) {
track.getTripStatistics().setCalorie(0);
myTracksProviderUtils.updateTrack(track);
Cursor cursor = null;
try {
cursor = myTracksProviderUtils.getWaypointCursor(track.getId(), -1L, -1);
if (cursor != null && cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as the
* first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
waypoint.getTripStatistics().setCalorie(0);
myTracksProviderUtils.updateWaypoint(waypoint);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Gets the low speed foot VO2 in ml/kg/min. This equation is appropriate for
* speed less than 4.5 mph like walking.
* Gets the next statistics waypoint from a cursor.
*
* @param speed the speed in meters per second
* @param myTracksProviderUtils the my tracks provider utils
* @param cursor the cursor
*/
private static Waypoint getNextStatisticsWaypoint(
MyTracksProviderUtils myTracksProviderUtils, Cursor cursor) {
if (cursor == null) {
return null;
}
while (cursor.moveToNext()) {
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
if (waypoint.getType() == WaypointType.STATISTICS) {
return waypoint;
}
}
return null;
}
/**
* Gets the running VO2 in ml/kg/min. This equation is appropriate for speeds
* greater than 5 mi/hr (or 3 mi/hr or greater if the subject is truly
* jogging).
*
* @param speed the speed in m/s
* @param grade the grade
*/
@VisibleForTesting
static double getLowSpeedFootVo2(double speed, double grade) {
// Change from meters per second to meters per minute
private static double getRunningVo2(double speed, double grade) {
// Change from m/s to m/min
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.1 means oxygen cost per meter of moving each kilogram (kg) of body
* weight while walking (horizontally). 1.8 means oxygen cost per meter of
* moving total body mass against gravity (vertically).
* 0.2 = oxygen cost per meter of moving each kg of body weight while
* running (horizontally). 0.9 = oxygen cost per meter of moving total body
* mass against gravity (vertically).
*/
return 0.1 * speed + 1.8 * speed * Math.abs(grade) + RESTING_VO2;
return 0.2 * speed + 0.9 * speed * grade + RESTING_VO2;
}
/**
* Gets the walking VO2 in ml/kg/min. This equation is appropriate for speed
* from 1.9 to 4 mi/hr.
*
* @param speed the speed in m/s
* @param grade the grade
*/
private static double getWalkingVo2(double speed, double grade) {
// Change from m/s to m/min
speed = speed / UnitConversions.S_TO_MIN;
/*
* 0.1 = oxygen cost per meter of moving each kilogram (kg) of body weight
* while walking (horizontally). 1.8 = oxygen cost per meter of moving total
* body mass against gravity (vertically).
*/
return 0.1 * speed + 1.8 * speed * grade + RESTING_VO2;
}
}
@@ -32,7 +32,9 @@ public class LocationUtils {
private static final String TAG = LocationUtils.class.getSimpleName();
private static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute
// 1 minute in milliseconds
private static final long MAX_LOCATION_AGE_MS = (long) (UnitConversions.MIN_TO_S
* UnitConversions.S_TO_MS);
private LocationUtils() {}
@@ -27,6 +27,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.text.TextUtils;
import android.util.Log;
/**
* Utilities to access preferences stored in {@link SharedPreferences}.
@@ -106,8 +107,6 @@ public class PreferencesUtils {
public static final boolean STATS_SHOW_COORDINATE_DEFAULT = false;
public static final boolean STATS_SHOW_GRADE_ELEVATION_DEFAULT = false;
public static final String STATS_UNITS_DEFAULT = "METRIC";
public static final boolean STATS_SHOW_CALORIE_DEFAULT = false;
public static final int STATS_WEIGHT_DEFAULT = 65; // in kilogram
// Track color
public static final String TRACK_COLOR_MODE_DEFAULT = "SINGLE";
@@ -124,6 +123,10 @@ public class PreferencesUtils {
public static final int TRACK_WIDGET_ITEM4_DEFAULT = 2; // average speed
public static final int VOICE_FREQUENCY_DEFAULT = 0;
public static final float WEIGHT_DEFAULT = 65.0f; // in kilogram
private static final String TAG = PreferencesUtils.class.getSimpleName();
private PreferencesUtils() {}
/**
@@ -194,6 +197,35 @@ public class PreferencesUtils {
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a float preference value.
*
* @param context the context
* @param keyId the key id
* @param defaultValue the default value
*/
public static float getFloat(Context context, int keyId, float defaultValue) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
return sharedPreferences.getFloat(getKey(context, keyId), defaultValue);
}
/**
* Sets a float preference value.
*
* @param context the context
* @param keyId the key id
* @param value the value
*/
@SuppressLint("CommitPrefEdits")
public static void setFloat(Context context, int keyId, float value) {
SharedPreferences sharedPreferences = context.getSharedPreferences(
Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
Editor editor = sharedPreferences.edit();
editor.putFloat(getKey(context, keyId), value);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor);
}
/**
* Gets a long preference value.
*
@@ -304,4 +336,37 @@ public class PreferencesUtils {
}
setString(context, keyId, list + ";" + value);
}
/**
* Stores the weight value, always in metric units.
*
* @param displayValue the display value
*/
public static void storeWeightValue(Context context, String displayValue) {
double value;
try {
value = Double.parseDouble(displayValue);
if (!PreferencesUtils.isMetricUnits(context)) {
value = value * UnitConversions.LB_TO_KG;
}
} catch (NumberFormatException e) {
Log.e(TAG, "invalid value " + displayValue);
value = PreferencesUtils.WEIGHT_DEFAULT;
}
PreferencesUtils.setFloat(context, R.string.weight_key, (float) value);
}
/**
* Gets the weight display value.
*
* @param context the context
*/
public static double getWeightDisplayValue(Context context) {
double value = PreferencesUtils.getFloat(
context, R.string.weight_key, PreferencesUtils.WEIGHT_DEFAULT);
if (!PreferencesUtils.isMetricUnits(context)) {
value = value * UnitConversions.KG_TO_LB;
}
return value;
}
}
@@ -16,8 +16,6 @@
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.CalorieUtils.ActivityType;
import com.google.android.maps.mytracks.R;
@@ -26,6 +24,7 @@ import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.view.View;
import android.widget.Spinner;
import android.widget.TextView;
import java.util.Locale;
@@ -40,9 +39,8 @@ public class StatsUtils {
private static final String COORDINATE_DEGREE = "\u00B0";
private static final String GRADE_PERCENTAGE = "%";
private static final String ELEVATION_FORMAT = "%1$.2f";
private static final String GRADE_FORMAT = "%1$d";
private static final String CALORIES_FORMAT = "%1$.0f";
private static final String CALORIES_FORMAT = "%1$,.0f";
private StatsUtils() {}
@@ -63,44 +61,48 @@ public class StatsUtils {
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
// Set speed/pace
double speed = isRecording && location != null && location.hasSpeed() ? location.getSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_speed), R.string.stats_speed,
R.string.stats_pace, speed, metricUnits, reportSpeed);
View speed = getView(activity, view, R.id.stats_speed);
speed.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
if (isRecording) {
double value = isRecording && location != null && location.hasSpeed() ? location.getSpeed()
: Double.NaN;
setSpeed(context, speed, R.string.stats_speed, R.string.stats_pace, value, metricUnits,
reportSpeed);
}
// Set elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context,
R.string.stats_show_grade_elevation_key,
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
boolean showGradeElevation = PreferencesUtils.getBoolean(
context, R.string.stats_show_grade_elevation_key,
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT) && isRecording;
View elevation = getView(activity, view, R.id.stats_elevation);
elevation.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation && isRecording) {
if (showGradeElevation) {
double altitude = location != null && location.hasAltitude() ? location.getAltitude()
: Double.NaN;
elevation.setVisibility(View.VISIBLE);
setElevationValue(context, elevation, -1, altitude, metricUnits);
} else {
elevation.setVisibility(View.GONE);
}
// Set coordinate
boolean showCoordinate = PreferencesUtils.getBoolean(context,
R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT);
View coordinateHorizontalLine = getView(activity, view, R.id.stats_coordinate_horizontal_line);
boolean showCoordinate = PreferencesUtils.getBoolean(
context, R.string.stats_show_coordinate_key, PreferencesUtils.STATS_SHOW_COORDINATE_DEFAULT)
&& isRecording;
View coordinateSeparator = getView(activity, view, R.id.stats_coordinate_separator);
View coordinateContainer = getView(activity, view, R.id.stats_coordinate_container);
if (showCoordinate && isRecording) {
if (coordinateSeparator != null) {
coordinateSeparator.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
}
coordinateContainer.setVisibility(showCoordinate ? View.VISIBLE : View.GONE);
if (showCoordinate) {
double latitude = location != null ? location.getLatitude() : Double.NaN;
double longitude = location != null ? location.getLongitude() : Double.NaN;
coordinateHorizontalLine.setVisibility(View.VISIBLE);
coordinateContainer.setVisibility(View.VISIBLE);
setCoordinateValue(
context, getView(activity, view, R.id.stats_latitude), R.string.stats_latitude, latitude);
setCoordinateValue(context, getView(activity, view, R.id.stats_longitude),
R.string.stats_longitude, longitude);
} else {
coordinateHorizontalLine.setVisibility(View.GONE);
coordinateContainer.setVisibility(View.GONE);
}
}
@@ -124,12 +126,12 @@ public class StatsUtils {
* @param view the containing view for finding views. If null, the activity
* cannot be null
* @param tripStatistics the trip statistics
* @param trackId the id of track, which is used to set calorie value. Does
* not handle the calorie If the value is
* {@link PreferencesUtils#RECORDING_TRACK_ID_DEFAULT}
* @param activityType the activity type
* @param trackIconValue the track icon value or null to hide the track icon
* spinner
*/
public static void setTripStatisticsValues(
Context context, Activity activity, View view, TripStatistics tripStatistics, long trackId) {
public static void setTripStatisticsValues(Context context, Activity activity, View view,
TripStatistics tripStatistics, ActivityType activityType, String trackIconValue) {
boolean metricUnits = PreferencesUtils.isMetricUnits(context);
boolean reportSpeed = PreferencesUtils.isReportSpeed(context);
@@ -138,43 +140,54 @@ public class StatsUtils {
setDistanceValue(
context, getView(activity, view, R.id.stats_distance), totalDistance, metricUnits);
// Set calorie
double calorie = tripStatistics == null || activityType == ActivityType.INVALID ? Double.NaN
: tripStatistics.getCalorie();
setCalorie(context, getView(activity, view, R.id.stats_calorie), calorie);
Spinner spinner = (Spinner) getView(activity, view, R.id.stats_activity_type_icon);
spinner.setVisibility(trackIconValue != null ? View.VISIBLE : View.GONE);
if (trackIconValue != null) {
TrackIconUtils.setIconSpinner(spinner, trackIconValue);
}
// Set total time
setTimeValue(context, getView(activity, view, R.id.stats_total_time), R.string.stats_total_time,
tripStatistics != null ? tripStatistics.getTotalTime() : -1L);
// Set moving time
setTimeValue(context, getView(activity, view, R.id.stats_moving_time),
R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
// Set average speed/pace
double averageSpeed = tripStatistics != null ? tripStatistics.getAverageSpeed() : Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_speed),
R.string.stats_average_speed, R.string.stats_average_pace, averageSpeed, metricUnits,
reportSpeed);
// Set moving time
setTimeValue(context, getView(activity, view, R.id.stats_moving_time),
R.string.stats_moving_time, tripStatistics != null ? tripStatistics.getMovingTime() : -1L);
// Set average moving speed/pace
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_moving_time),
R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed,
metricUnits, reportSpeed);
// Set max speed/pace
double maxSpeed = tripStatistics == null ? Double.NaN : tripStatistics.getMaxSpeed();
setSpeed(context, getView(activity, view, R.id.stats_max_speed), R.string.stats_max_speed,
R.string.stats_fastest_pace, maxSpeed, metricUnits, reportSpeed);
// Set average moving speed/pace
double averageMovingSpeed = tripStatistics != null ? tripStatistics.getAverageMovingSpeed()
: Double.NaN;
setSpeed(context, getView(activity, view, R.id.stats_average_moving_speed),
R.string.stats_average_moving_speed, R.string.stats_average_moving_pace, averageMovingSpeed,
metricUnits, reportSpeed);
// Set grade/elevation
boolean showGradeElevation = PreferencesUtils.getBoolean(context,
R.string.stats_show_grade_elevation_key,
PreferencesUtils.STATS_SHOW_GRADE_ELEVATION_DEFAULT);
View gradeElevationHorizontalLine = getView(
activity, view, R.id.stats_grade_elevation_horizontal_line);
View gradeElevationSeparator = getView(activity, view, R.id.stats_grade_elevation_separator);
View gradeElevationContainer = getView(activity, view, R.id.stats_grade_elevation_container);
gradeElevationSeparator.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
gradeElevationContainer.setVisibility(showGradeElevation ? View.VISIBLE : View.GONE);
if (showGradeElevation) {
gradeElevationHorizontalLine.setVisibility(View.VISIBLE);
gradeElevationContainer.setVisibility(View.VISIBLE);
// Set grade
double minGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMinGrade();
double maxGrade = tripStatistics == null ? Double.NaN : tripStatistics.getMaxGrade();
@@ -194,33 +207,6 @@ public class StatsUtils {
R.string.stats_min, minElevation, metricUnits);
setElevationValue(context, getView(activity, view, R.id.stats_elevation_max),
R.string.stats_max, maxElevation, metricUnits);
} else {
gradeElevationHorizontalLine.setVisibility(View.GONE);
gradeElevationContainer.setVisibility(View.GONE);
}
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) {
// Set calories
boolean showCalorie = PreferencesUtils.getBoolean(
context, R.string.stats_show_calorie_key, PreferencesUtils.STATS_SHOW_CALORIE_DEFAULT);
View calorieHorizontalLine = getView(activity, view, R.id.stats_calorie_horizontal_line);
View calorieContainer = getView(activity, view, R.id.stats_calorie_container);
if (showCalorie) {
calorieHorizontalLine.setVisibility(View.VISIBLE);
calorieContainer.setVisibility(View.VISIBLE);
double calories = Double.NaN;
if (tripStatistics != null) {
calories = tripStatistics.getCalorie();
}
MyTracksProviderUtils myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
Track track = myTracksProviderUtils.getTrack(trackId);
ActivityType activityType = track == null ? ActivityType.INVALID
: CalorieUtils.getActivityType(context, track.getCategory());
setCalorie(context, getView(activity, view, R.id.stats_calorie), activityType, calories);
} else {
calorieHorizontalLine.setVisibility(View.GONE);
calorieContainer.setVisibility(View.GONE);
}
}
}
@@ -241,6 +227,19 @@ public class StatsUtils {
setItem(context, view, reportSpeed ? speedLabelId : paceLabelId, parts[0], parts[1]);
}
/**
* Sets calorie.
*
* @param context the context
* @param view the containing view
* @param calorie the value of calorie
*/
private static void setCalorie(Context context, View view, double calorie) {
String value = Double.isNaN(calorie) ? null
: String.format(Locale.getDefault(), CALORIES_FORMAT, calorie);
setItem(context, view, R.string.stats_calorie, value, context.getString(R.string.unit_calorie));
}
/**
* Sets distance value.
*
@@ -286,11 +285,11 @@ public class StatsUtils {
unit = null;
} else {
if (metricUnits) {
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_meter);
} else {
elevation *= UnitConversions.M_TO_FT;
value = String.format(Locale.getDefault(), ELEVATION_FORMAT, elevation);
value = StringUtils.formatDecimal(elevation);
unit = context.getString(R.string.unit_feet);
}
}
@@ -322,28 +321,8 @@ public class StatsUtils {
private static void setCoordinateValue(
Context context, View view, int labelId, double coordinate) {
String value = Double.isNaN(coordinate) || Double.isInfinite(coordinate) ? null
: Location.convert(coordinate, Location.FORMAT_DEGREES);
setItem(context, view, labelId, value, COORDINATE_DEGREE);
}
/**
* Sets calorie.
*
* @param context the context
* @param view the containing view
* @param activityType the activity type
* @param calorie the value of calorie
*/
private static void setCalorie(
Context context, View view, ActivityType activityType, double calorie) {
if (activityType == ActivityType.INVALID) {
view.setVisibility(View.GONE);
} else {
view.setVisibility(View.VISIBLE);
setItem(context, view, R.string.stats_calorie,
String.format(Locale.getDefault(), CALORIES_FORMAT, calorie),
context.getString(R.string.unit_calorie));
}
: Location.convert(coordinate, Location.FORMAT_DEGREES) + COORDINATE_DEGREE;
setItem(context, view, labelId, value, null);
}
/**
@@ -355,7 +334,8 @@ public class StatsUtils {
* @param value the value, can be null
* @param unit the unit. Null to hide the unit
*/
private static void setItem(Context context, View view, int labelId, String value, String unit) {
private static void setItem(
Context context, View view, int labelId, CharSequence value, CharSequence unit) {
TextView labelTextView = (TextView) view.findViewById(R.id.stats_label);
TextView valueTextView = (TextView) view.findViewById(R.id.stats_value);
TextView unitTextView = (TextView) view.findViewById(R.id.stats_unit);
@@ -81,7 +81,7 @@ public class StringUtils {
*/
public static String formatElapsedTime(long time) {
/*
* Temporary workaround for DateUtils.formatElapsedTime(time / 1000). In API
* Temporary workaround for DateUtils.formatElapsedTime(time * MS_TO_S). In API
* level 17, it returns strings like "1:0:00" instead of "1:00:00", which
* breaks several unit tests.
*/
@@ -91,7 +91,7 @@ public class StringUtils {
long hours = 0;
long minutes = 0;
long seconds = 0;
long elapsedSeconds = time / 1000;
long elapsedSeconds = (long) (time * UnitConversions.MS_TO_S);
if (elapsedSeconds >= 3600) {
hours = elapsedSeconds / 3600;
@@ -149,6 +149,19 @@ public class StringUtils {
}
}
public static String formatWeight(double value) {
return formatDecimal(value, 1);
}
public static String formatDecimal(double value) {
return formatDecimal(value, 2);
}
private static String formatDecimal(double value, int precision) {
String result = String.format(Locale.getDefault(), "%1$,." + precision + "f", value);
return result.replaceAll("[0]*$", "").replaceAll("\\.$", "");
}
/**
* Gets the distance in an array of two strings. The first string is the
* distance. The second string is the unit. The first string is null if the
@@ -183,7 +196,7 @@ public class StringUtils {
unitId = R.string.unit_feet;
}
}
result[0] = String.format("%.2f", distance);
result[0] = formatDecimal(distance);
result[1] = context.getString(unitId);
return result;
}
@@ -217,13 +230,13 @@ public class StringUtils {
speed *= UnitConversions.KM_TO_MI;
}
if (reportSpeed) {
result[0] = String.format("%.2f", speed);
result[0] = StringUtils.formatDecimal(speed);
} else {
// convert from hours to minutes
double pace = speed == 0 ? 0.0 : 60.0 / speed;
int minutes = (int) pace;
int seconds = (int) Math.round((pace - minutes) * 60.0);
result[0] = String.format("%d:%02d", minutes, seconds);
result[0] = String.format(Locale.US, "%d:%02d", minutes, seconds);
}
return result;
}
@@ -301,7 +314,7 @@ public class StringUtils {
if (fractional != null) {
// Regex ensures fractional part is in (0,1)
float fractionalSeconds = Float.parseFloat(fractional);
long fractionalMillis = (long) (fractionalSeconds * 1000.0f);
long fractionalMillis = (long) (fractionalSeconds * UnitConversions.S_TO_MS);
time += fractionalMillis;
}
@@ -350,7 +363,7 @@ public class StringUtils {
}
int[] parts = new int[3];
long seconds = time / 1000;
long seconds = (long) (time * UnitConversions.MS_TO_S);
parts[0] = (int) (seconds % 60);
int minutes = (int) (seconds / 60);
parts[1] = minutes % 60;
@@ -21,8 +21,13 @@ import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.Pair;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -100,10 +105,10 @@ public class TrackIconUtils {
*/
public static int getIconDrawable(String iconValue) {
if (iconValue == null || iconValue.equals("")) {
return R.drawable.ic_track_walk;
return R.drawable.ic_track_generic;
}
Pair<Integer, Integer> pair = map.get(iconValue);
return pair == null ? R.drawable.ic_track_walk : pair.second;
return pair == null ? R.drawable.ic_track_generic : pair.second;
}
/**
@@ -137,6 +142,9 @@ public class TrackIconUtils {
* @param activityType the activity type
*/
public static String getIconValue(Context context, String activityType) {
if (activityType == null || activityType.equals("")) {
return "";
}
if (inList(context, activityType, airplane)) {
return AIRPLANE;
}
@@ -196,6 +204,35 @@ public class TrackIconUtils {
return output;
}
public static void setIconSpinner(Spinner spinner, String iconValue) {
@SuppressWarnings("unchecked")
ArrayAdapter<StringBuilder> adapter = (ArrayAdapter<StringBuilder>) spinner.getAdapter();
StringBuilder stringBuilder = adapter.getItem(0);
stringBuilder.delete(0, stringBuilder.length());
stringBuilder.append(iconValue);
adapter.notifyDataSetChanged();
}
public static ArrayAdapter<StringBuilder> getIconSpinnerAdapter(
final Context context, String iconValue) {
return new ArrayAdapter<StringBuilder>(context, android.R.layout.simple_spinner_item,
new StringBuilder[] { new StringBuilder(iconValue) }) {
@Override
public View getView(int position, View convertView, android.view.ViewGroup parent) {
ImageView imageView = convertView != null ? (ImageView) convertView
: new ImageView(getContext());
Bitmap source = BitmapFactory.decodeResource(
context.getResources(), TrackIconUtils.getIconDrawable(getItem(position).toString()));
if (ApiAdapterFactory.getApiAdapter().isSpinnerBackgroundLight()) {
source = TrackIconUtils.invertBitmap(source);
}
imageView.setImageBitmap(source);
imageView.setPadding(4, 4, -4, -4);
return imageView;
}
};
}
/**
* Returns true if the activity type is in the list.
*
@@ -0,0 +1,70 @@
/*
* Copyright 2013 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.util;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.maps.mytracks.R;
import android.content.Context;
/**
* Utilities for updating track.
*
* @author Jimmy Shih
*/
public class TrackUtils {
private TrackUtils() {}
public static void updateTrack(Context context, Track track, String name, String category,
String description, MyTracksProviderUtils myTracksProviderUtils,
TrackRecordingServiceConnection trackRecordingServiceConnection, boolean newWeight) {
if (name != null) {
track.setName(name);
}
boolean updateCalorie = false;
if (category != null) {
updateCalorie = !category.equals(track.getCategory()) || newWeight;
track.setCategory(category);
track.setIcon(TrackIconUtils.getIconValue(context, category));
}
if (description != null) {
track.setDescription(description);
}
track.setModifiedTime(System.currentTimeMillis());
myTracksProviderUtils.updateTrack(track);
if (updateCalorie) {
if (track.getId() == PreferencesUtils.getLong(context, R.string.recording_track_id_key)) {
// Update calorie through track recording service
TrackRecordingServiceConnectionUtils.updateCalorie(trackRecordingServiceConnection);
} else {
CalorieUtils.updateTrackCalorie(context, track);
}
}
boolean driveSync = PreferencesUtils.getBoolean(
context, R.string.drive_sync_key, PreferencesUtils.DRIVE_SYNC_DEFAULT);
if (driveSync) {
PreferencesUtils.addToList(context, R.string.drive_edited_list_key,
PreferencesUtils.DRIVE_EDITED_LIST_DEFAULT, String.valueOf(track.getId()));
}
}
}
@@ -24,11 +24,13 @@ public class UnitConversions {
private UnitConversions() {}
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// Time
// multiplication factor to convert seconds to milliseconds
public static final double S_TO_MS = 1000.0;
// multiplication factor to convert milliseconds to seconds
public static final double MS_TO_S = 1 / 1000.0;
public static final double MS_TO_S = 1 / S_TO_MS;
// multiplication factor to convert minutes to seconds
public static final double MIN_TO_S = 60.0;
@@ -42,15 +44,11 @@ public class UnitConversions {
// multiplication factor to convert minutes to hours
public static final double MIN_TO_HR = 1 / HR_TO_MIN;
// Distance
// multiplication factor to convert kilometers to miles
public static final double KM_TO_MI = 0.621371192;
// multiplication factor to convert kilograms to pounds
public static final double KG_TO_LB = 2.2046;
// multiplication factor to convert pounds to kilograms
public static final double LB_TO_KG = 1 / KG_TO_LB;
// multiplication factor to convert miles to kilometers
public static final double MI_TO_KM = 1 / KM_TO_MI;
@@ -60,13 +58,11 @@ public class UnitConversions {
// multiplication factor to convert feet to miles
public static final double FT_TO_MI = 1 / MI_TO_FT;
// multiplication factor to covert kilometers to meters
public static final double KM_TO_M = 1000.0;
// multiplication factor to convert meters to kilometers
public static final double M_TO_KM = 1 / 1000.0;
public static final double KM_TO_M = 1 / M_TO_KM;
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = 3.6;
public static final double M_TO_KM = 1 / KM_TO_M;
// multiplication factor to convert meters to miles
public static final double M_TO_MI = M_TO_KM * KM_TO_MI;
@@ -74,12 +70,33 @@ public class UnitConversions {
// multiplication factor to convert meters to feet
public static final double M_TO_FT = M_TO_MI * MI_TO_FT;
// Weight
// multiplication factor to convert kilograms to pounds
public static final double KG_TO_LB = 2.2046;
// multiplication factor to convert pounds to kilograms
public static final double LB_TO_KG = 1 / KG_TO_LB;
// Calories
// multiplication factor to convert milliliters to liters
public static final double ML_TO_L = 1 / 1000.0;
// multiplication factor to convert liter to kcal
public static final double L_TO_KCAL = 5.0;
// multiplication factor to convert watt to kgm/min
public static final double W_TO_KGM = 6.12;
// multiplication factor to convert kgm to kcal
public static final double KGM_TO_KCAL = 1 / 427.0;
// Others
// multiplication factor to convert meters per second to kilometers per hour
public static final double MS_TO_KMH = M_TO_KM / (S_TO_MIN * MIN_TO_HR);
// multiplication factor to convert degrees to radians
public static final double DEG_TO_RAD = Math.PI / 180.0;
// multiplication factor to convert Kcal to joules.
public static final double KCAL_TO_J = 4184;
// multiplication factor to convert joules to Kcal.
public static final double J_TO_KCAL = 1 / KCAL_TO_J;
}
@@ -394,6 +394,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int elevationGainIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN);
int minGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE);
int maxGradeIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE);
int calorieIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.CALORIE);
int photoUrlIndex = cursor.getColumnIndexOrThrow(WaypointsColumns.PHOTOURL);
Waypoint waypoint = new Waypoint();
@@ -496,7 +497,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
tripStatistics.setMaxGrade(cursor.getFloat(maxGradeIndex));
hasTripStatistics = true;
}
if (!cursor.isNull(calorieIndex)) {
tripStatistics.setCalorie(cursor.getFloat(calorieIndex));
hasTripStatistics = true;
}
if (hasTripStatistics) {
waypoint.setTripStatistics(tripStatistics);
}
@@ -739,6 +743,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
values.put(WaypointsColumns.ELEVATIONGAIN, tripStatistics.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, tripStatistics.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, tripStatistics.getMaxGrade());
values.put(WaypointsColumns.CALORIE, tripStatistics.getCalorie());
}
values.put(WaypointsColumns.PHOTOURL, waypoint.getPhotoUrl());
@@ -124,7 +124,7 @@ public interface TracksColumns extends BaseColumns {
+ MODIFIEDTIME + " INTEGER, " // modified time
+ SHAREDWITHME + " INTEGER, " // shared with me
+ SHAREDOWNER + " STRING, " // shared owner
+ CALORIE + " INTEGER);"; // calorie
+ CALORIE + " FLOAT);"; // calorie
public static final String[] COLUMNS = { _ID, // id
NAME, // name
@@ -190,6 +190,6 @@ public interface TracksColumns extends BaseColumns {
ContentTypeIds.LONG_TYPE_ID, // modified time
ContentTypeIds.BOOLEAN_TYPE_ID, // shared with me
ContentTypeIds.STRING_TYPE_ID, // shared owner
ContentTypeIds.INT_TYPE_ID // calorie
ContentTypeIds.FLOAT_TYPE_ID // calorie
};
}
@@ -70,7 +70,8 @@ public interface WaypointsColumns extends BaseColumns {
public static final String ELEVATIONGAIN = "elevationgain"; // elevation gain
public static final String MINGRADE = "mingrade"; // min grade
public static final String MAXGRADE = "maxgrade"; // max grade
public static final String CALORIE = "calorie"; // calorie
public static final String PHOTOURL = "photoUrl"; // url for the photo
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
@@ -104,6 +105,7 @@ public interface WaypointsColumns extends BaseColumns {
+ ELEVATIONGAIN + " FLOAT, "
+ MINGRADE + " FLOAT, "
+ MAXGRADE + " FLOAT, "
+ CALORIE + " FLOAT, "
+ PHOTOURL + " STRING"
+ ");";
@@ -138,6 +140,7 @@ public interface WaypointsColumns extends BaseColumns {
ELEVATIONGAIN,
MINGRADE,
MAXGRADE,
CALORIE,
PHOTOURL
};
@@ -172,6 +175,7 @@ public interface WaypointsColumns extends BaseColumns {
FLOAT_TYPE_ID, // elevation gain
FLOAT_TYPE_ID, // min grade
FLOAT_TYPE_ID, // max grade
FLOAT_TYPE_ID, // calorie
STRING_TYPE_ID // photo url
};
}
@@ -225,8 +225,8 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(i + locationOffset, (i + locationOffset) * .001, MOVING_SPEED,
startTime + (timeOffset + i) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.FOOT,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.WALKING,
PreferencesUtils.WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
assertEquals((timeOffset + i) * TEN_SECONDS, tripStatistics.getTotalTime());
@@ -272,8 +272,8 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(
locationOffset, locationOffset * .001, 0, startTime + (i + timeOffset) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, false, ActivityType.FOOT,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, false, ActivityType.WALKING,
PreferencesUtils.WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
assertEquals((i + timeOffset) * TEN_SECONDS, tripStatistics.getTotalTime());
@@ -310,8 +310,8 @@ public class TripStatisticsUpdaterTest extends TestCase {
Location location = getLocation(i + locationOffset, (i + locationOffset) * .001, 99999,
startTime + (timeOffset + i) * TEN_SECONDS);
tripStatisticsUpdater.addLocation(location,
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.FOOT,
PreferencesUtils.STATS_WEIGHT_DEFAULT);
PreferencesUtils.RECORDING_DISTANCE_INTERVAL_DEFAULT, true, ActivityType.WALKING,
PreferencesUtils.WEIGHT_DEFAULT);
tripStatistics = tripStatisticsUpdater.getTripStatistics();
assertTrue(tripStatistics.getMovingTime() <= tripStatistics.getTotalTime());
@@ -1,112 +0,0 @@
/*
* Copyright 2013 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.util;
import android.location.Location;
import android.location.LocationManager;
import junit.framework.TestCase;
/**
* A unit test for {@link CalorieUtils}.
*
* @author youtaol
*/
public class CalorieUtilsTest extends TestCase {
Location start = new Location(LocationManager.GPS_PROVIDER);
Location stop = new Location(LocationManager.GPS_PROVIDER);
private double grade = 0.07;
private int weight = 80;
private final long TIME_INTERVAL = 1000l;
@Override
protected void setUp() throws Exception {
super.setUp();
// Make the time interval is not 0
stop.setTime(start.getTime() + TIME_INTERVAL);
}
/**
* Checks using foot calculation equation.
*/
public void testGetCalories_foot() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.getFootCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
/**
* Checks whether using foot calculation equation while grade is negative.
*/
public void testGetCalories_footNegativeGrade() {
double actualGrade = -5;
double expectGrade = 0;
double actual = CalorieUtils.getCalorie(start, stop, actualGrade, weight,
CalorieUtils.ActivityType.FOOT);
double expected = CalorieUtils.getFootCalorie(start, stop, expectGrade, weight);
assertEquals(expected, actual);
}
/**
* Checks using cycling calculation equation.
*/
public void testGetCalories_cycling() {
double actual = CalorieUtils.getCalorie(start, stop, grade, weight,
CalorieUtils.ActivityType.CYCLING);
double expected = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertEquals(expected, actual);
}
/**
* Checks using running VO2 equation.
*/
public void testGetVO2_running() {
double actual = CalorieUtils.getFootVo2(CalorieUtils.CRTICAL_SPEED_RUNNING * 2, grade);
double expected = CalorieUtils.getHighSpeedFootVo2(CalorieUtils.CRTICAL_SPEED_RUNNING * 2,
grade);
assertEquals(expected, actual);
}
/**
* Checks using walking VO2 equation.
*/
public void testGetVO2_walking() {
// Test at half the critical speed
double footSpeed = CalorieUtils.CRTICAL_SPEED_RUNNING / 2.0;
double actual = CalorieUtils.getFootVo2(footSpeed, grade);
double expected = CalorieUtils.getLowSpeedFootVo2(footSpeed, grade);
assertEquals(expected, actual);
}
/**
* Checks calculating cycling calorie. 175 is the watt usage for a 90 kg bike
* + rider to go 9 m/s (20 mph or 32 km/h) on the flat. <a href=
* "http://www.ideafit.com/fitness-library/calculating-caloric-expenditure-0"
* >Reference</a>
*/
public void testCalculateCalorieCycling() {
grade = 0;
weight = 90;
start.setSpeed(9);
stop.setSpeed(9);
double expected = 175.0 * (TIME_INTERVAL / 1000) * UnitConversions.J_TO_KCAL;
double actual = CalorieUtils.getCyclingCalorie(start, stop, grade, weight);
assertTrue((actual - expected) / expected < 0.02);
}
}