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