merge heads

This commit is contained in:
Jimmy Shih
2012-04-09 14:07:57 -07:00
parent 97f4103e67
commit 7b800914f6
62 changed files with 2918 additions and 2996 deletions
+4 -49
View File
@@ -147,18 +147,10 @@ limitations under the License.
<activity
android:label="@string/menu_aggregated_statistics"
android:name="com.google.android.apps.mytracks.AggregatedStatsActivity" />
<activity
android:exported="false"
android:name="com.google.android.apps.mytracks.ChartActivity" >
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value="com.google.android.apps.mytracks.SearchActivity" />
</activity>
<activity android:name="com.google.android.apps.mytracks.ExportActivity"/>
<activity
android:label="@string/menu_help"
android:name="com.google.android.apps.mytracks.HelpActivity" />
<activity
android:label="@string/my_tracks_importer"
android:name="com.google.android.apps.mytracks.ImportActivity"
@@ -179,17 +171,6 @@ limitations under the License.
<data android:scheme="file" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name="com.google.android.apps.mytracks.MapActivity" >
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value="com.google.android.apps.mytracks.SearchActivity" />
</activity>
<activity
android:launchMode="singleTop"
android:name="com.google.android.apps.mytracks.SearchActivity" >
@@ -211,30 +192,8 @@ limitations under the License.
<category android:name="android.intent.category.PREFERENCE" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name="com.google.android.apps.mytracks.StatsActivity" >
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value="com.google.android.apps.mytracks.SearchActivity" />
</activity>
<activity android:name="com.google.android.apps.mytracks.TrackDetailActivity" />
<activity
android:label="@string/track_edit_title"
android:name="com.google.android.apps.mytracks.TrackEditActivity" >
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.TrackEditActivity" />
<activity android:name="com.google.android.apps.mytracks.TrackListActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -262,10 +221,6 @@ limitations under the License.
android:name="android.app.default_searchable"
android:value="com.google.android.apps.mytracks.SearchActivity" />
</activity>
<activity
android:exported="false"
android:label="@string/welcome_title"
android:name="com.google.android.apps.mytracks.WelcomeActivity" />
<activity android:name="com.google.android.apps.mytracks.io.backup.BackupActivity"/>
<activity android:name="com.google.android.apps.mytracks.io.backup.RestoreActivity"/>
<activity android:name="com.google.android.apps.mytracks.io.backup.RestoreChooserActivity"/>
+2 -2
View File
@@ -4,8 +4,8 @@ applications that wish to use these files should consult the appropriate
licenses or the JAR's authors.
Android Support Package:
android-support-v4.jar
- see http://developer.android.com/sdk/compatibility-library.html
android-support-v4-r7-googlemaps.jar
- see https://github.com/petedoyle/android-support-v4-googlemaps
Google APIs Client Library for Java:
commons-codec-1.3.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_stats" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_stats_press" />
<item android:drawable="@drawable/right_arrow_stats" />
</selector>
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_track" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_track_press" />
<item android:drawable="@drawable/right_arrow_track" />
</selector>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/left_arrow_chart" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_chart_press" />
<item android:drawable="@drawable/left_arrow_chart" />
</selector>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<Button
android:id="@+id/help_about"
android:text="@string/help_about"
style="@style/BottomButton" />
<Button
android:id="@+id/help_ok"
android:text="@string/generic_ok"
style="@style/BottomButton" />
</merge>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/left_arrow_stats" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_stats_press" />
<item android:drawable="@drawable/left_arrow_stats" />
</selector>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<Button
android:id="@+id/track_edit_cancel"
android:text="@string/generic_cancel"
style="@style/BottomButton" />
<Button
android:id="@+id/track_edit_save"
android:text="@string/generic_save"
style="@style/BottomButton" />
</merge>
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical" >
<ScrollView
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent" >
<include layout="@layout/welcome" />
</ScrollView>
<LinearLayout style="@style/BottomButtonBar" >
<include layout="@layout/help_buttons" />
</LinearLayout>
</LinearLayout>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/left_arrow_track" />
<item android:state_pressed="true"
android:drawable="@drawable/left_arrow_track_press" />
<item android:drawable="@drawable/left_arrow_track" />
</selector>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<Button
android:id="@+id/help_ok"
android:text="@string/generic_ok"
style="@style/BottomButton" />
<Button
android:id="@+id/help_about"
android:text="@string/help_about"
style="@style/BottomButton" />
</merge>
+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical"
android:padding="8dp" >
<ImageView
android:contentDescription="@string/my_tracks_app_name"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:scaleType="fitStart"
android:src="@drawable/arrow_icon" />
<TextView
android:text="@string/help_my_tracks_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_my_tracks_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_location_precision_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_location_precision_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_gps_fix_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_gps_fix_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_data_connection_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_data_connection_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_elevation_precision_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_elevation_precision_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_battery_life_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_battery_life_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_share_friends_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_share_friends_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_share_visibility_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_share_visibility_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_split_track_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_split_track_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_saved_files_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_saved_files_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_waypoint_marker_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_waypoint_marker_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_statistics_marker_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_statistics_marker_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_delete_tracks_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_delete_tracks_answer"
style="@style/WelcomeAnswerTextView" />
<TextView
android:text="@string/help_edit_tracks_question"
style="@style/WelcomeQuestionTextView" />
<TextView
android:text="@string/help_edit_tracks_answer"
style="@style/WelcomeAnswerTextView" />
</LinearLayout>
</merge>
@@ -486,16 +486,14 @@ limitations under the License.
</TableLayout>
</LinearLayout>
</ScrollView>
<LinearLayout style="@style/ButtonLayout">
<LinearLayout style="@style/ButtonSpacer"/>
<Button android:id="@+id/waypointdetails_save"
android:layout_gravity="left"
<LinearLayout style="@style/BottomButtonBar" >
<Button
android:id="@+id/waypointdetails_save"
android:text="@string/generic_save"
style="@style/Button"/>
<Button android:id="@+id/waypointdetails_cancel"
android:layout_gravity="right"
style="@style/BottomButton" />
<Button
android:id="@+id/waypointdetails_cancel"
android:text="@string/generic_cancel"
style="@style/Button"/>
<LinearLayout style="@style/ButtonSpacer"/>
style="@style/BottomButton" />
</LinearLayout>
</LinearLayout>
@@ -35,21 +35,18 @@ limitations under the License.
android:text="@string/marker_list_pick_one"
android:layout_weight="1"/>
</LinearLayout>
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<LinearLayout style="@style/ButtonLayout">
<LinearLayout style="@style/ButtonSpacer"/>
<Button android:id="@+id/waypointslist_btn_insert_statistics"
android:layout_gravity="left"
<LinearLayout style="@style/BottomButtonBar" >
<Button
android:id="@+id/waypointslist_btn_insert_statistics"
android:text="@string/marker_list_insert_statistics"
style="@style/Button"/>
<Button android:id="@+id/waypointslist_btn_insert_waypoint"
android:layout_gravity="right"
style="@style/BottomButton" />
<Button
android:id="@+id/waypointslist_btn_insert_waypoint"
android:text="@string/marker_list_insert_waypoint"
style="@style/Button"/>
<LinearLayout style="@style/ButtonSpacer"/>
style="@style/BottomButton" />
</LinearLayout>
</LinearLayout>
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_height="match_parent"
android:layout_width="match_parent" >
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical" >
<TabWidget
android:id="@android:id/tabs"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_width="match_parent"
android:orientation="horizontal" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_height="0dp"
android:layout_weight="0"
android:layout_width="0dp" />
<FrameLayout
android:id="@+id/realtabcontent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="match_parent" />
</LinearLayout>
</TabHost>
+36 -79
View File
@@ -14,98 +14,55 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical"
>
android:orientation="vertical" >
<ScrollView
android:background="@color/black"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent"
>
<LinearLayout
android:layout_width="fill_parent" >
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical"
android:padding="4dp"
>
android:padding="8dp" >
<TextView
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="@string/track_edit_track_name"
/>
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
>
<ImageView
android:contentDescription="@string/track_icon_description"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:src="@drawable/track"
/>
<EditText
android:inputType="textAutoCorrect|textCapWords"
android:hint="@string/track_edit_track_name"
android:id="@+id/track_edit_track_name"
android:imeOptions="actionNext"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:selectAllOnFocus="true"
android:singleLine="true"
/>
</LinearLayout>
<TextView
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="@string/track_edit_activity_type"
/>
<AutoCompleteTextView
android:id="@+id/track_edit_activity_type"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:hint="@string/track_edit_activity_type_hint"
android:imeOptions="actionNext"/>
<TextView
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="@string/track_edit_track_description"
/>
android:text="@string/track_edit_name"
style="@style/TrackEditLabelTextView" />
<EditText
android:inputType="textAutoCorrect|textCapSentences"
android:hint="@string/track_edit_track_description"
android:id="@+id/track_edit_track_description"
android:drawableLeft="@drawable/track"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:hint="@string/track_edit_name"
android:id="@+id/track_edit_name"
android:imeOptions="actionNext"
android:inputType="textAutoCorrect|textCapWords"
android:singleLine="true"
style="@style/TrackEditValueEditText" />
<TextView
android:text="@string/track_edit_activity_type"
style="@style/TrackEditLabelTextView" />
<AutoCompleteTextView
android:hint="@string/track_edit_activity_type_hint"
android:id="@+id/track_edit_activity_type"
android:imeOptions="actionNext"
android:singleLine="true"
style="@style/TrackEditValueEditText" />
<TextView
android:text="@string/track_edit_description"
style="@style/TrackEditLabelTextView" />
<EditText
android:hint="@string/track_edit_description"
android:id="@+id/track_edit_description"
android:imeOptions="actionDone"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:inputType="textAutoCorrect|textCapSentences"
android:maxLines="50"
android:selectAllOnFocus="true"
android:singleLine="false"
/>
style="@style/TrackEditValueEditText" />
</LinearLayout>
</ScrollView>
<LinearLayout style="@style/ButtonLayout">
<LinearLayout style="@style/ButtonSpacer"/>
<Button
android:id="@+id/track_edit_save"
android:layout_gravity="left"
android:text="@string/generic_save"
style="@style/Button"
/>
<Button
android:id="@+id/track_edit_cancel"
android:layout_gravity="right"
android:text="@string/generic_cancel"
style="@style/Button"
/>
<LinearLayout style="@style/ButtonSpacer"/>
<LinearLayout style="@style/BottomButtonBar" >
<include layout="@layout/track_edit_buttons" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Copyright 2012 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false"
android:drawable="@drawable/right_arrow_chart" />
<item android:state_pressed="true"
android:drawable="@drawable/right_arrow_chart_press" />
<item android:drawable="@drawable/right_arrow_chart" />
</selector>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<Button
android:id="@+id/track_edit_save"
android:text="@string/generic_save"
style="@style/BottomButton" />
<Button
android:id="@+id/track_edit_cancel"
android:text="@string/generic_cancel"
style="@style/BottomButton" />
</merge>
+4 -138
View File
@@ -14,142 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical">
<ScrollView
android:background="@color/white"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_width="fill_parent">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<ImageView
android:contentDescription="@string/my_tracks_app_name"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:scaleType="fitStart"
android:src="@drawable/arrow_icon"/>
<TextView
android:text="@string/welcome_my_tracks_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_my_tracks_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_app_navigation_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_app_navigation_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_add_graph_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_add_graph_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_gps_fix_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_gps_fix_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_send_google_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_send_google_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_share_friends_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_share_friends_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_waypoint_marker_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_waypoint_marker_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_statistics_marker_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_statistics_marker_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_data_connection_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_data_connection_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_battery_life_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_battery_life_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_share_file_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_share_file_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_location_precision_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_location_precision_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_elevation_precision_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_elevation_precision_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_edit_accuracy_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_edit_accuracy_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_split_track_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_split_track_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_delete_all_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_delete_all_answer"
style="@style/WelcomeTextView"/>
<TextView
android:text="@string/welcome_maps_sharing_question"
style="@style/WelcomeTextView.Question"/>
<TextView
android:text="@string/welcome_maps_sharing_answer"
style="@style/WelcomeTextView"/>
</LinearLayout>
</ScrollView>
<LinearLayout style="@style/ButtonLayout">
<LinearLayout style="@style/ButtonSpacer"/>
<Button
android:id="@+id/welcome_ok"
android:layout_gravity="left"
android:text="@string/generic_ok"
style="@style/Button"/>
<Button
android:id="@+id/welcome_about"
android:layout_gravity="right"
android:text="@string/welcome_about"
style="@style/Button"/>
<LinearLayout style="@style/ButtonSpacer"/>
</LinearLayout>
</LinearLayout>
android:layout_width="fill_parent" >
<include layout="@layout/help_content"/>
</ScrollView>
+25 -25
View File
@@ -17,82 +17,82 @@ limitations under the License.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:icon="@drawable/menu_stop_recording"
android:id="@+id/menu_stop_recording"
android:id="@+id/track_detail_stop_recording"
android:showAsAction="always"
android:title="@string/menu_stop_recording"/>
<item
android:icon="@drawable/menu_insert_marker"
android:id="@+id/menu_insert_marker"
android:id="@+id/track_detail_insert_marker"
android:showAsAction="always"
android:title="@string/menu_insert_marker"/>
<item
android:icon="@drawable/menu_play"
android:id="@+id/menu_play"
android:id="@+id/track_detail_play"
android:showAsAction="always"
android:title="@string/menu_play"/>
<item
android:icon="@drawable/menu_share"
android:id="@+id/menu_share"
android:id="@+id/track_detail_share"
android:showAsAction="always"
android:title="@string/menu_share">
<menu>
<item
android:id="@+id/menu_share_map"
android:id="@+id/track_detail_share_map"
android:title="@string/menu_share_map"/>
<item
android:id="@+id/menu_share_fusion_table"
android:id="@+id/track_detail_share_fusion_table"
android:title="@string/menu_share_fusion_table"/>
<item android:id="@+id/menu_share_gpx"/>
<item android:id="@+id/menu_share_kml"/>
<item android:id="@+id/menu_share_csv"/>
<item android:id="@+id/menu_share_tcx"/>
<item android:id="@+id/track_detail_share_gpx"/>
<item android:id="@+id/track_detail_share_kml"/>
<item android:id="@+id/track_detail_share_csv"/>
<item android:id="@+id/track_detail_share_tcx"/>
</menu>
</item>
<item
android:icon="@drawable/ic_menu_waypoints"
android:id="@+id/menu_markers"
android:id="@+id/track_detail_markers"
android:title="@string/menu_markers"/>
<item
android:id="@+id/menu_send_google"
android:id="@+id/track_detail_send_google"
android:title="@string/menu_send_google"/>
<item
android:id="@+id/menu_save"
android:id="@+id/track_detail_save"
android:title="@string/menu_save">
<menu>
<item android:id="@+id/menu_save_gpx"/>
<item android:id="@+id/menu_save_kml"/>
<item android:id="@+id/menu_save_csv"/>
<item android:id="@+id/menu_save_tcx"/>
<item android:id="@+id/track_detail_save_gpx"/>
<item android:id="@+id/track_detail_save_kml"/>
<item android:id="@+id/track_detail_save_csv"/>
<item android:id="@+id/track_detail_save_tcx"/>
</menu>
</item>
<item
android:id="@+id/menu_edit"
android:id="@+id/track_detail_edit"
android:title="@string/menu_edit"/>
<item
android:id="@+id/menu_delete"
android:id="@+id/track_detail_delete"
android:title="@string/menu_delete"/>
<item
android:icon="@android:drawable/ic_menu_mylocation"
android:id="@+id/menu_my_location"
android:id="@+id/track_detail_my_location"
android:title="@string/menu_my_location"/>
<item
android:icon="@android:drawable/ic_menu_mapmode"
android:id="@+id/menu_satellite_mode"
android:id="@+id/track_detail_satellite_mode"
android:title="@string/menu_satellite_mode"/>
<item
android:icon="@drawable/ic_menu_chartsettings"
android:id="@+id/menu_chart_settings"
android:id="@+id/track_detail_chart_settings"
android:title="@string/menu_chart_settings"/>
<item
android:icon="@drawable/ic_menu_sensorstatus"
android:id="@+id/menu_sensor_state"
android:id="@+id/track_detail_sensor_state"
android:title="@string/menu_sensor_state"/>
<item
android:icon="@drawable/menu_settings"
android:id="@+id/menu_settings"
android:id="@+id/track_detail_settings"
android:title="@string/menu_settings"/>
<item
android:icon="@drawable/menu_help"
android:id="@+id/menu_help"
android:id="@+id/track_detail_help"
android:title="@string/menu_help"/>
</menu>
+17 -10
View File
@@ -17,38 +17,45 @@ limitations under the License.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:icon="@drawable/menu_record_track"
android:id="@+id/menu_record_track"
android:id="@+id/track_list_record_track"
android:showAsAction="always"
android:title="@string/menu_record_track"/>
<item
android:icon="@drawable/menu_stop_recording"
android:id="@+id/menu_stop_recording"
android:id="@+id/track_list_stop_recording"
android:showAsAction="always"
android:title="@string/menu_stop_recording"/>
<item
android:actionViewClass="android.widget.SearchView"
android:icon="@drawable/menu_search"
android:id="@+id/menu_search"
android:id="@+id/track_list_search"
android:showAsAction="always|collapseActionView"
android:title="@string/menu_search"/>
<item
android:id="@+id/menu_import_all"
android:id="@+id/track_list_import_all"
android:title="@string/menu_import_all"/>
<item
android:id="@+id/menu_export_all"
android:title="@string/menu_export_all"/>
android:id="@+id/track_list_export_all"
android:title="@string/menu_export_all">
<menu>
<item android:id="@+id/track_list_export_gpx"/>
<item android:id="@+id/track_list_export_kml"/>
<item android:id="@+id/track_list_export_csv"/>
<item android:id="@+id/track_list_export_tcx"/>
</menu>
</item>
<item
android:id="@+id/menu_delete_all"
android:id="@+id/track_list_delete_all"
android:title="@string/menu_delete_all"/>
<item
android:id="@+id/menu_aggregated_statistics"
android:id="@+id/track_list_aggregated_statistics"
android:title="@string/menu_aggregated_statistics"/>
<item
android:icon="@drawable/menu_settings"
android:id="@+id/menu_settings"
android:id="@+id/track_list_settings"
android:title="@string/menu_settings"/>
<item
android:icon="@drawable/menu_help"
android:id="@+id/menu_help"
android:id="@+id/track_list_help"
android:title="@string/menu_help"/>
</menu>
-29
View File
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<array name="left_icons">
<item>@drawable/left_arrow_chart_stateful</item>
<item>@drawable/left_arrow_track_stateful</item>
<item>@drawable/left_arrow_stats_stateful</item>
</array>
<array name="right_icons">
<item>@drawable/right_arrow_stats_stateful</item>
<item>@drawable/right_arrow_chart_stateful</item>
<item>@drawable/right_arrow_track_stateful</item>
</array>
</resources>
+181 -214
View File
@@ -700,6 +700,155 @@ limitations under the License.
Waiting for a better GPS fix. Make sure the sky is visible.
</string>
<!-- Help page -->
<string name="help_about"
translation_description="In the 'Help' page, the button on the bottom to open the 'About My
Tracks' dialog.">
About My Tracks
</string>
<string name="help_battery_life_answer"
translation_description="In the 'Help' page, the answer on battery life.">
The GPS receiver draws a relatively high amount of power, so a fully charged battery typically
survives less than 5 hours. Battery life depends on many factors, including signal strength,
battery freshness, and tasks running in parallel. Switching off some or all of the phone\'s
radios will lengthen battery life. Putting the phone into Airplane mode will yield the best
results.
</string>
<string name="help_battery_life_question"
translation_description="In the 'Help' page, the question on battery life.">
How long does the battery last when recording tracks?
</string>
<string name="help_data_connection_answer"
translation_description="In the 'Help' page, the answer on data connection.">
Cell signal or data connection is not required to record GPS tracks. However, if there is no
data connection, you will not be able to see a map. Instead, you will see a grey screen or a
partial map. Tracks, represented as simple lines, can still be recorded even if there is no data
connection.
</string>
<string name="help_data_connection_question"
translation_description="In the 'Help' page, the question on data connection.">
Do I need cell signal and data connection to use My Tracks?
</string>
<string name="help_delete_tracks_answer"
translation_description="In the 'Help' page, the answer on delete tracks.">
From the track list, select \"Delete all\" when not recording. This permanently deletes the
recorded tracks from memory. If you have saved tracks or backups on the SD card, you must delete
them separately. Tracks uploaded to Google Maps, Google Fusion Tables, and Google Doc must also
be deleted separately.
</string>
<string name="help_delete_tracks_question"
translation_description="In the 'Help' page, the question on delete tracks.">
How do I delete all the tracks from the phone?
</string>
<string name="help_edit_tracks_answer"
translation_description="In the 'Help' page, the answer on edit tracks.">
Currently, you can\'t with My Tracks. But you can upload your tracks to Google or save them to
the SD card to modify them there.
</string>
<string name="help_edit_tracks_question"
translation_description="In the 'Help' page, the question on edit tracks.">
How can I edit or correct tracks for accuracy?
</string>
<string name="help_elevation_precision_answer"
translation_description="In the 'Help' page, the help content answer on elevation
precision.">
GPS receivers measure elevations much less precisely than latitude and longitude. Elevations
can sometimes be off by a few hundred feet, though relative changes are tracked more precisely.
To get a good elevation measurement, make sure that you have a strong GPS signal and wait a few
minutes for the elevation to settle to a stable value.
</string>
<string name="help_elevation_precision_question"
translation_description="In the 'Help' page, the question on elevation precision.">
How precise is the elevation measurement?
</string>
<string name="help_gps_fix_answer"
translation_description="In the 'Help' page, the answer on the GPS fix message.">
For My Tracks to work, your phone needs to be in line-of-sight contact with the GPS satellites.
If the line-of-sight is obstructed by trees, buildings or canyon walls, you may not get an
accurate fix. If you see the message, wait a minute or two. If your device does not achieve a
fix, move to an area clear of obstructions and try again.
</string>
<string name="help_gps_fix_question"
translation_description="In the 'Help' page, the question on the GPS fix message.">
Why does the application sometimes display \"Waiting for a better GPS fix.\"?
</string>
<string name="help_location_precision_answer"
translation_description="In the 'Help' page, the answer on location precision.">
Under optimal conditions, the location error can be +/- 20 feet (6 meters). However, precision
depends on the quality of the GPS signal. The blue circle around the orange location arrow
indicates estimated precision. (You may need to zoom in or out to see the circle.) You can
change the GPS precision at Settings -> Recording -> GPS accuracy.
</string>
<string name="help_location_precision_question"
translation_description="In the 'Help' page, the question on location precision.">
How precise are the recorded tracks?
</string>
<string name="help_my_tracks_answer"
translation_description="In the 'Help' page, the answer on what is My Tracks.">
My Tracks allows you to record GPS tracks and visualize your hiking, running, biking or any
other activity while watching live performance statistics. My Tracks makes it easy to archive
your training history with Google Maps, Google Fusion Tables, and Google Docs, and share your
activities with friends and the world.
</string>
<string name="help_my_tracks_question"
translation_description="In the 'Help' page, the question on what is My Tracks.">
What is My Tracks?
</string>
<string name="help_saved_files_answer"
translation_description="In the 'Help' page, the answer on saved files.">
You can use Google Earth or similar applications to visualize and edit GPS tracks.
</string>
<string name="help_saved_files_question"
translation_description="In the 'Help' page, the question on saved files.">
What can I do with tracks after saving them to the SD card or sharing such files with friends?
</string>
<string name="help_share_friends_answer"
translation_description="In the 'Help' page, the answer on sharing with friends.">
You can share a track as a link to Google Map or Google Fusion Table (you will be prompted to
upload the track first) or as a GPX, KML, CSV, or TCX file in an e-mail attachment.
</string>
<string name="help_share_friends_question"
translation_description="In the 'Help' page, the question on sharing with friends.">
How do I share tracks with friends and the rest of the world?
</string>
<string name="help_share_visibility_answer"
translation_description="In the 'Help' page, the answer on share visibility.">
By default, tracks sent to Google Maps and Google Fusion Tables are public. You can change the
visibility of your tracks in Google Maps and Google Fusion. For Google Maps, you can change the
default from public to unlisted in Settings -> Sharing.
</string>
<string name="help_share_visibility_question"
translation_description="In the 'Help' page, the question on share visibility.">
Who can see the tracks I send to Google Maps and Google Fusion Tables?
</string>
<string name="help_split_track_answer"
translation_description="In the 'Help' page, the answer on splitting long tracks.">
Tracks on Google Maps and Google Fusion Tables are limited to certain sizes.
</string>
<string name="help_split_track_question"
translation_description="In the 'Help' page, the question on splitting long tracks.">
Why do very long tracks split into multiple segments upon sending them to Google Maps and
Google Fusion Tables?
</string>
<string name="help_statistics_marker_answer"
translation_description="In the 'Help' page, the answer on statistics markers.">
Statistics markers are used to segment a track to calculate statistics for each segment. For
example, let\'s say you are recording a bike ride up a hill, you can insert a statistics marker
at the bottom of the hill and another at the top.
</string>
<string name="help_statistics_marker_question"
translation_description="In the 'Help' page, the question on statistics markers.">
What are statistics markers?
</string>
<string name="help_waypoint_marker_answer"
translation_description="In the 'Help' page, the answer on waypoint markers.">
Waypoint markers are used to annotate a location.
</string>
<string name="help_waypoint_marker_question"
translation_description="In the 'Help' page, the question on waypoint markers.">
What are waypoint markers?
</string>
<!-- Import -->
<string name="import_error"
translation_description="When importing all the tracks, a toast message indicating import error
@@ -923,8 +1072,9 @@ limitations under the License.
Edit
</string>
<string name="menu_export_all"
translation_description="In the menu, to export all the tracks to the SD card.">
Export all
translation_description="In the menu, to export all the tracks to the SD card. This menu item
has a submenu.">
Export all...
</string>
<string name="menu_export_all_format"
translation_description="Under the 'Export all' menu option, the option to export all tracks as
@@ -970,7 +1120,8 @@ limitations under the License.
Satellite mode
</string>
<string name="menu_save"
translation_description="In the menu, to save a track to the SD card.">
translation_description="In the menu, to save a track to the SD card. This menu item has a
submenu.">
Save to SD card...
</string>
<string name="menu_save_format"
@@ -2014,6 +2165,10 @@ limitations under the License.
</string>
<!-- Track -->
<string name="track_detail_chart_tab"
translation_description="In the track detail page, the title of the tab showing the chart.">
Chart
</string>
<string name="track_detail_delete_confirm_message"
translation_description="When deleting a track, the confirmation message informing the user that
the track and its markers will be permanently deleted from the phone.">
@@ -2025,33 +2180,41 @@ limitations under the License.
Play Store.">
You need Google Earth to play the track. Do you want to install from the Play Store?
</string>
<string name="track_detail_map_tab"
translation_description="In the track detail page, the title of the tab showing the map.">
Map
</string>
<string name="track_detail_stats_tab"
translation_description="In the track detail page, the title of the tab showing the stats.">
Stats
</string>
<string name="track_icon_description"
translation_description="The accessibility description for the track icon.">
Track
</string>
<string name="track_edit_activity_type"
translation_description="In the page to edit a track, the title for the text field to edit the
translation_description="In the page to edit a track, the label for the text field to edit the
activity type, including some examples of activity types.">
Activity type (e.g. hiking, cycling, running...)
Activity type (e.g. hiking, cycling...)
</string>
<string name="track_edit_activity_type_hint"
translation_description="In the page to edit a track, the hint in the 'Activity type' text field
when it is empty.">
Activity type
</string>
<string name="track_edit_title"
translation_description="The title of the page to edit a track.">
Edit track
</string>
<string name="track_edit_track_description"
translation_description="In the page to edit a track, the title for the text field to edit the
<string name="track_edit_description"
translation_description="In the page to edit a track, the label for the text field to edit the
track description.">
Track description
Description
</string>
<string name="track_edit_track_name"
translation_description="In the page to edit a track, the title for the text field to edit the
<string name="track_edit_name"
translation_description="In the page to edit a track, the label for the text field to edit the
track name.">
Track name
Name
</string>
<string name="track_edit_new_track_title"
translation_description="The title of the track edit page when saving a new track.">
New Track
</string>
<string name="track_list_delete_all_confirm_message"
translation_description="When deleting all the tracks, the confirmation dialog message informing
@@ -2778,205 +2941,9 @@ limitations under the License.
waypoint
</string>
<!-- Welcome Page -->
<string name="welcome_about"
translation_description="In the 'Welcome' page, the button on the bottom to open the 'About My
Tracks' dialog.">
About My Tracks
</string>
<string name="welcome_add_graph_answer"
translation_description="In the 'Welcome' page, the help content answer on adding graphs to the
graphs view.">
In the graphs view, Menu -> Chart settings.
</string>
<string name="welcome_add_graph_question"
translation_description="In the 'Welcome' page, the help content question on adding graphs to
the graphs view.">
How do I add more graphs?
</string>
<string name="welcome_app_navigation_answer"
translation_description="In the 'Welcome' page, the help content answer on app navigation.">
Touch the map. Left and right arrows will appear. Tap on these arrows to cycle between the map,
statistics, and graphs view.
</string>
<string name="welcome_app_navigation_question"
translation_description="In the 'Welcome' page, the help content question on app navigation.">
How do I see the map, statistics, and graphs view?
</string>
<string name="welcome_battery_life_answer"
translation_description="In the 'Welcome' page, the help content answer on battery life.">
The GPS receiver draws a relatively high amount of power, so a fully charged battery typically
survives less than 5 hours. Battery life depends on many factors, including signal strength,
battery freshness, and tasks running in parallel. Switching off some or all of the phone\'s
radios will lengthen battery life. Putting the phone into Airplane mode will yield the best
results.
</string>
<string name="welcome_battery_life_question"
translation_description="In the 'Welcome' page, the help content question on battery life.">
How long does the battery last when recording tracks?
</string>
<string name="welcome_data_connection_answer"
translation_description="In the 'Welcome' page, the help content answer on data connection.">
Cell signal or data connection is not required to record GPS tracks. However, if there is no
data connection, you will not be able to see a map. Instead, you will see a grey screen or a
partial map. Tracks, represented as simple lines, can still be recorded even if there is no data
connection.
</string>
<string name="welcome_data_connection_question"
translation_description="In the 'Welcome' page, the help content question on data connection.">
Do I need cell signal and data connection to use My Tracks?
</string>
<string name="welcome_delete_all_answer"
translation_description="In the 'Welcome' page, the help content answer on deleting all
tracks.">
Menu -> Tracks -> Delete all. This permanently deletes the recorded tracks from memory. If you
have saved tracks to the SD card, you must delete them separately. Tracks uploaded to Google
Maps, Google Fusion Tables, and Google Docs must also be deleted separately.
</string>
<string name="welcome_delete_all_question"
translation_description="In the 'Welcome' page, the help content question on deleting all
tracks.">
How do I delete all the tracks from the phone?
</string>
<string name="welcome_edit_accuracy_answer"
translation_description="In the 'Welcome' page, the help content answer on editing points.">
Once you upload tracks to Google Maps, Google Fusion Tables, or Google Docs, you can move,
delete or insert individual track points as desired.
</string>
<string name="welcome_edit_accuracy_question"
translation_description="In the 'Welcome' page, the help content question on editing points.">
How can I edit or correct tracks for accuracy?
</string>
<string name="welcome_elevation_precision_answer"
translation_description="In the 'Welcome' page, the help content answer on elevation
precision.">
GPS receivers measure elevations much less precisely than latitude and longitude. Elevations
can sometimes be off by a few hundred feet, though relative changes are tracked more precisely.
To get a good elevation measurement, make sure that you have a strong GPS signal and wait a few
minutes for the elevation to settle to a stable value.
</string>
<string name="welcome_elevation_precision_question"
translation_description="In the 'Welcome' page, the help content question on elevation
precision.">
How precise is the elevation measurement?
</string>
<string name="welcome_gps_fix_answer"
translation_description="In the 'Welcome' page, the help content answer on the GPS fix
message.">
For My Tracks to work, your phone needs to be in line-of-sight contact with the GPS satellites.
If the line-of-sight is obstructed by trees, buildings or canyon walls, you may not get an
accurate fix. If you see the message, wait a minute or two. If your device does not achieve a
fix, move to an area clear of obstructions and try again.
</string>
<string name="welcome_gps_fix_question"
translation_description="In the 'Welcome' page, the help content question on the GPS fix
message.">
Why does the application sometimes display \"Waiting for a better GPS fix. Make sure the sky is
visible.\"?
</string>
<string name="welcome_location_precision_answer"
translation_description="In the 'Welcome' page, the help content answer on location precision.">
Under optimal conditions, the location error can be +/- 20 feet (6 meters). However, precision
depends on the quality of the GPS signal. The blue circle around the orange location arrow
indicates estimated precision. (You may need to zoom in or out to see the circle.) You can
change the GPS precision at Settings -> Recording -> GPS accuracy. If the precision is too low,
at the top of the screen, you will see the message \"Waiting for a better GPS fix.\".
</string>
<string name="welcome_location_precision_question"
translation_description="In the 'Welcome' page, the help content question on location
precision.">
How precise are the recorded tracks?
</string>
<string name="welcome_maps_sharing_answer"
translation_description="In the 'Welcome' page, the help content answer on Google Maps
visibility.">
By default, new maps are public. To change the default to unlisted, uncheck Settings -> Sharing
-> Make new maps public. You can change the visibility of all your maps at My places in Google
Maps.
</string>
<string name="welcome_maps_sharing_question"
translation_description="In the 'Welcome' page, the help content question on Google Maps
visibility.">
Who can see the maps I send to Google Maps?
</string>
<string name="welcome_my_tracks_answer"
translation_description="In the 'Welcome' page, the help content answer on what is My Tracks.">
My Tracks allows you to record GPS tracks and visualize your hiking, running, biking or any
other activity while watching live performance statistics. My Tracks makes it easy to archive
your training history with Google Maps, Google Fusion Tables, and Google Docs, and share your
activities with friends and the world.
</string>
<string name="welcome_my_tracks_question"
translation_description="In the 'Welcome' page, the help content question on what is My
Tracks.">
What is My Tracks?
</string>
<string name="welcome_send_google_answer"
translation_description="In the 'Welcome' page, the help content answer on sending a track to
Google.">
Tap the Options button located at the bottom right corner of the map view and select \"Send to
Google...\".
</string>
<string name="welcome_send_google_question"
translation_description="In the 'Welcome' page, the help content question on sending a track to
Google.">
How do I send my tracks to Google Maps, Google Fusion Tables, and Google Docs?
</string>
<string name="welcome_share_file_answer"
translation_description="In the 'Welcome' page, the help content answer on sharing a file.">
You can use Google Earth or similar applications to visualize and edit GPS tracks.
</string>
<string name="welcome_share_file_question"
translation_description="In the 'Welcome' page, the help content question on sharing a file.">
What can I do with tracks after saving them to the SD card or sharing such files with friends?
</string>
<string name="welcome_share_friends_answer"
translation_description="In the 'Welcome' page, the help content answer on sharing a track with
friends.">
Tap the Options button located at the bottom right corner of the map view and select \"Share
with friends...\". You can share the track as a link to Google Map or Google Fusion Table
(you will be prompted to upload the track first) or as a GPX, KML, CSV, or TCX file in an e-mail
attachment.
</string>
<string name="welcome_share_friends_question"
translation_description="In the 'Welcome' page, the help content question on sharing a track
with friends.">
How do I share tracks with friends and the rest of the world?
</string>
<string name="welcome_split_track_answer"
translation_description="In the 'Welcome' page, the help content answer on splitting long
tracks.">
Tracks on Google Maps and Google Fusion Tables are limited to certain sizes.
</string>
<string name="welcome_split_track_question"
translation_description="In the 'Welcome' page, the help content question on splitting long
tracks.">
Why do very long tracks split into multiple segments upon sending them to Google Maps and
Google Fusion Tables?
</string>
<string name="welcome_statistics_marker_answer"
translation_description="In the 'Welcome' page, the help content answer on statistics markers.">
Statistics markers are used to segment a track to calculate statistics for each segment. For
example, let\'s say you are recording a bike ride up a hill, you can insert a statistics marker
at the bottom of the hill and another at the top by pressing Menu -> Markers -> Insert
statistics.
</string>
<string name="welcome_statistics_marker_question"
translation_description="In the 'Welcome' page, the help content question on statistics
markers.">
What are statistics markers?
</string>
<!-- Welcome -->
<string name="welcome_title"
translation_description="In the 'Welcome' page, the title of the page.">
My Tracks Welcome
</string>
<string name="welcome_waypoint_marker_answer"
translation_description="In the 'Welcome' page, the help content answer on waypoint markers.">
Waypoint markers are used to annotate a location. For example, if you are recording a track and
you are close to an interesting place, press Menu -> Markers -> Insert waypoint.
</string>
<string name="welcome_waypoint_marker_question"
translation_description="In the 'Welcome' page, the help content question on waypoint markers.">
What are waypoint markers?
translation_description="The title of the welcome dialog.">
Welcome to My Tracks
</string>
</resources>
+29 -36
View File
@@ -17,34 +17,17 @@ limitations under the License.
<resources>
<style name="ThemeSelector" parent="android:Theme" />
<!-- Button -->
<style name="Button">
<item name="android:layout_height">fill_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
<item name="android:maxLines">2</item>
<item name="android:textSize">14sp</item>
</style>
<!-- Button layout -->
<style name="ButtonLayout">
<item name="android:background">@drawable/list_bar</item>
<!-- The style for the bottom button bar -->
<style name="BottomButtonBar" parent="@android:style/DeviceDefault.ButtonBar">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:measureWithLargestChild">true</item>
<item name="android:orientation">horizontal</item>
<item name="android:paddingLeft">2dp</item>
<item name="android:paddingRight">2dp</item>
<item name="android:paddingTop">4dp</item>
</style>
<!-- Button spacer -->
<style name="ButtonSpacer">
<!-- The style for the bottom button -->
<style name="BottomButton" parent="@android:style/Widget.DeviceDefault.Button.Borderless">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_weight">0.25</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_width">0dp</item>
<item name="android:orientation">horizontal</item>
<item name="android:visibility">gone</item>
</style>
<style name="SensorStateLabel">
@@ -125,28 +108,38 @@ limitations under the License.
<item name="android:textColor">@color/stat_label_text</item>
</style>
<!-- The TextView showing the stats in the track list item -->
<style name="TrackListItemStatsTextView" parents="@android:style/TextAppearance.DeviceDefault.Small">
<!-- The style for the label in track_edit.xml -->
<style name="TrackEditLabelTextView" parent="@android:style/TextAppearance.DeviceDefault.Medium">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
</style>
<!-- The style for the value in track_edit.xml -->
<style name="TrackEditValueEditText" parent="@android:style/TextAppearance.DeviceDefault.Medium">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:selectAllOnFocus">true</item>
</style>
<!-- The style for the stats in track_list_item.xml -->
<style name="TrackListItemStatsTextView" parent="@android:style/TextAppearance.DeviceDefault.Small">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">@color/green</item>
</style>
<!-- Welcome page TextView -->
<style name="WelcomeTextView">
<!-- The style for the answer in welcome.xml -->
<style name="WelcomeAnswerTextView" parent="@android:style/TextAppearance.DeviceDefault.Small">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:paddingLeft">10sp</item>
<item name="android:paddingRight">10sp</item>
<item name="android:textColor">@color/black</item>
<item name="android:textSize">16sp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:paddingBottom">8dp</item>
</style>
<!-- Welcome page TextView question -->
<style name="WelcomeTextView.Question">
<item name="android:paddingTop">10sp</item>
<!-- The style for the question in welcome.xml -->
<style name="WelcomeQuestionTextView" parent="@android:style/TextAppearance.DeviceDefault.Medium">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textStyle">bold</item>
<item name="android:paddingTop">8dp</item>
</style>
<!-- Widget TextView -->
@@ -1,486 +0,0 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.location.Location;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.ZoomControls;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* An activity that displays a chart from the track point provider.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartActivity extends Activity implements TrackDataListener {
private static final int DIALOG_CHART_SETTINGS_ID = 0;
private final DoubleBuffer elevationBuffer =
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer =
new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
private TrackDataHub dataHub;
// Stats gathered from received data.
private double profileLength = 0;
private long startTime = -1;
private Location lastLocation;
private double trackMaxSpeed;
// Modes of operation
private boolean metricUnits = true;
private boolean reportSpeed = true;
/*
* UI elements:
*/
private ChartView chartView;
private LinearLayout busyPane;
private ZoomControls zoomControls;
/**
* A runnable that can be posted to the UI thread. It will remove the spinner
* (if any), enable/disable zoom controls and orange pointer as appropriate
* and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
public void run() {
// Get a local reference in case it's set to null concurrently with this.
TrackDataHub localDataHub = dataHub;
if (localDataHub == null || isFinishing()) {
return;
}
busyPane.setVisibility(View.GONE);
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(localDataHub.isRecordingSelected());
chartView.invalidate();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "ChartActivity.onCreate");
super.onCreate(savedInstanceState);
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setContentView(R.layout.mytracks_charts);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
chartView = new ChartView(this);
LayoutParams params =
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.addView(chartView, params);
busyPane = (LinearLayout) findViewById(R.id.elevation_busypane);
zoomControls = (ZoomControls) findViewById(R.id.elevation_zoom);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomIn();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomOut();
}
});
}
@Override
protected void onResume() {
super.onResume();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
}
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
dataHub = null;
super.onPause();
}
private void zoomIn() {
chartView.zoomIn();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
private void zoomOut() {
chartView.zoomOut();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
private void setMode(Mode newMode) {
if (chartView.getMode() != newMode) {
chartView.setMode(newMode);
dataHub.reloadDataForListener(this);
}
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != DIALOG_CHART_SETTINGS_ID) {
return null;
}
View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
radioGroup.check(chartView.getMode() == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance : R.id.chart_settings_by_time);
final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_elevation);
checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_cadence);
checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view.findViewById(
R.id.chart_settings_heart_rate);
// set checkboxes values
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
checkBoxes[i].setChecked(chartView.isChartValueSeriesEnabled(i));
}
checkBoxes[ChartView.SPEED_SERIES].setText(reportSpeed
? R.string.stat_speed : R.string.stat_pace);
return new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance
? Mode.BY_DISTANCE
: Mode.BY_TIME);
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chartView.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
}
chartView.postInvalidate();
}
})
.setTitle(R.string.menu_chart_settings)
.setView(view)
.create();
}
/**
* Shows the chart settings dialog
*/
public void showChartSettingsDialog() {
showDialog(DIALOG_CHART_SETTINGS_ID);
}
/**
* Given a location, creates a new data point for the chart. A data point is
* an array double[3 or 6], where:
* data[0] = the time or distance
* data[1] = the elevation
* data[2] = the speed
* and possibly:
* data[3] = power
* data[4] = cadence
* data[5] = heart rate
*
* This must be called in order for each point.
*
* @param location the location to get data for (this method takes ownership of that location)
* @param result the resulting point to fill out
*/
@VisibleForTesting
void fillDataPoint(Location location, double result[]) {
double timeOrDistance = Double.NaN,
elevation = Double.NaN,
speed = Double.NaN,
power = Double.NaN,
cadence = Double.NaN,
heartRate = Double.NaN;
if (location instanceof MyTracksLocation &&
((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet();
if (sensorData.hasPower()
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
&& sensorData.getPower().hasValue()) {
power = sensorData.getPower().getValue();
}
if (sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
cadence = sensorData.getCadence().getValue();
}
if (sensorData.hasHeartRate()
&& sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorData.getHeartRate().hasValue()) {
heartRate = sensorData.getHeartRate().getValue();
}
}
// TODO: Account for segment splits?
Mode mode = chartView.getMode();
switch (mode) {
case BY_DISTANCE:
if (lastLocation != null) {
double d = lastLocation.distanceTo(location);
if (metricUnits) {
profileLength += d;
} else {
profileLength += d * UnitConversions.KM_TO_MI;
}
}
timeOrDistance = profileLength * UnitConversions.M_TO_KM;
break;
case BY_TIME:
if (startTime == -1) {
// Base case
startTime = location.getTime();
}
timeOrDistance = (location.getTime() - startTime);
break;
default:
Log.w(TAG, "ChartActivity unknown mode: " + mode);
}
elevationBuffer.setNext(metricUnits
? location.getAltitude()
: location.getAltitude() * UnitConversions.M_TO_FT);
elevation = elevationBuffer.getAverage();
if (lastLocation == null) {
if (Math.abs(location.getSpeed() - 128) > 1) {
speedBuffer.setNext(location.getSpeed());
}
} else if (TripStatisticsBuilder.isValidSpeed(
location.getTime(), location.getSpeed(), lastLocation.getTime(),
lastLocation.getSpeed(), speedBuffer)
&& (location.getSpeed() <= trackMaxSpeed)) {
speedBuffer.setNext(location.getSpeed());
}
speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
if (!reportSpeed) {
if (speed != 0) {
// Format as hours per unit
speed = (60.0 / speed);
} else {
speed = Double.NaN;
}
}
// Keep a copy so the location can be reused.
lastLocation = location;
if (result != null) {
result[0] = timeOrDistance;
result[1] = elevation;
result[2] = speed;
result[3] = power;
result[4] = cadence;
result[5] = heartRate;
}
}
@Override
public void onProviderStateChange(ProviderState state) {
// We don't care.
}
@Override
public void onCurrentLocationChanged(Location loc) {
// We don't care.
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
runOnUiThread(new Runnable() {
@Override
public void run() {
busyPane.setVisibility(View.VISIBLE);
}
});
}
@Override
public void onTrackUpdated(Track track) {
if (track == null || track.getStatistics() == null) {
trackMaxSpeed = 0.0;
return;
}
trackMaxSpeed = track.getStatistics().getMaxSpeed();
}
@Override
public void clearTrackPoints() {
profileLength = 0;
lastLocation = null;
startTime = -1;
elevationBuffer.reset();
chartView.reset();
speedBuffer.reset();
pendingPoints.clear();
runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.resetScroll();
}
});
}
@Override
public void onNewTrackPoint(Location loc) {
if (LocationUtils.isValidLocation(loc)) {
double[] point = new double[6];
fillDataPoint(loc, point);
pendingPoints.add(point);
}
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// Still account for the point in the smoothing buffers.
fillDataPoint(loc, null);
}
@Override
public void onSegmentSplit() {
// Do nothing.
}
@Override
public void onNewTrackPointsDone() {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
runOnUiThread(updateChart);
}
@Override
public void clearWaypoints() {
chartView.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint wpt) {
chartView.addWaypoint(wpt);
}
@Override
public void onNewWaypointsDone() {
runOnUiThread(updateChart);
}
@Override
public boolean onUnitsChanged(boolean metric) {
if (metricUnits == metric) {
return false;
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.requestLayout();
}
});
return true;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (reportSpeed == speed) {
return false;
}
reportSpeed = speed;
chartView.setReportSpeed(speed, this);
runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.requestLayout();
}
});
return true;
}
@VisibleForTesting
ChartView getChartView() {
return chartView;
}
@VisibleForTesting
void setTrackMaxSpeed(double maxSpeed) {
trackMaxSpeed = maxSpeed;
}
}
@@ -0,0 +1,518 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.ChartView.Mode;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import com.google.common.annotations.VisibleForTesting;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.ZoomControls;
import java.util.ArrayList;
import java.util.EnumSet;
/**
* A fragment to display track chart to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class ChartFragment extends Fragment implements TrackDataListener {
// Android reports 128 when the speed is invalid
private static final int INVALID_SPEED = 128;
private final DoubleBuffer elevationBuffer = new DoubleBuffer(
Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer = new DoubleBuffer(Constants.SPEED_SMOOTHING_FACTOR);
private final ArrayList<double[]> pendingPoints = new ArrayList<double[]>();
private TrackDataHub trackDataHub;
// Stats gathered from the received data
private double totalDistance = 0.0;
private long startTime = -1L;
private Location lastLocation = null;
private double trackMaxSpeed = 0.0;
// Modes of operation
private boolean metricUnits = true;
private boolean reportSpeed = true;
// UI elements
private ChartView chartView;
private LinearLayout busyPane;
private ZoomControls zoomControls;
/**
* A runnable that will remove the spinner (if any), enable/disable zoom
* controls and orange pointer as appropriate and redraw.
*/
private final Runnable updateChart = new Runnable() {
@Override
public void run() {
if (trackDataHub == null) {
return;
}
busyPane.setVisibility(View.GONE);
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(isRecording());
chartView.invalidate();
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Create a chartView here to store data thus won't need to reload all the
* data on every onStart or onResume.
*/
chartView = new ChartView(getActivity());
};
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.mytracks_charts, container, false);
busyPane = (LinearLayout) view.findViewById(R.id.elevation_busypane);
zoomControls = (ZoomControls) view.findViewById(R.id.elevation_zoom);
zoomControls.setOnZoomInClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomIn();
}
});
zoomControls.setOnZoomOutClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
zoomOut();
}
});
return view;
}
@Override
public void onStart() {
super.onStart();
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
layout.addView(chartView, layoutParams);
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
getActivity().runOnUiThread(updateChart);
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
@Override
public void onStop() {
super.onStop();
ViewGroup layout = (ViewGroup) getActivity().findViewById(R.id.elevation_chart);
layout.removeView(chartView);
}
/**
* Sets the chart view mode.
*
* @param mode the chart view mode
*/
public void setMode(Mode mode) {
if (chartView.getMode() != mode) {
chartView.setMode(mode);
reloadTrackDataHub();
}
}
/**
* Gets the chart view mode.
*/
public Mode getMode() {
return chartView.getMode();
}
/**
* Enables or disables the chart value series.
*
* @param index the index of the series
* @param enabled true to enable, false to disable
*/
public void setChartValueSeriesEnabled(int index, boolean enabled) {
chartView.setChartValueSeriesEnabled(index, enabled);
}
/**
* Returns true if the chart value series is enabled.
*
* @param index the index of the series
*/
public boolean isChartValueSeriesEnabled(int index) {
return chartView.isChartValueSeriesEnabled(index);
}
/**
* Returns true to report speed instead of pace.
*/
public boolean isReportSpeed() {
return reportSpeed;
}
/**
* Updates the chart.
*/
public void update() {
chartView.postInvalidate();
}
@Override
public void onProviderStateChange(ProviderState state) {
// We don't care.
}
@Override
public void onCurrentLocationChanged(Location loc) {
// We don't care.
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
busyPane.setVisibility(View.VISIBLE);
}
});
}
@Override
public void onTrackUpdated(Track track) {
if (track == null || track.getStatistics() == null) {
trackMaxSpeed = 0.0;
return;
}
trackMaxSpeed = track.getStatistics().getMaxSpeed();
}
@Override
public void clearTrackPoints() {
totalDistance = 0.0;
startTime = -1L;
lastLocation = null;
elevationBuffer.reset();
speedBuffer.reset();
pendingPoints.clear();
chartView.reset();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.resetScroll();
}
});
}
@Override
public void onNewTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) {
double[] data = new double[6];
fillDataPoint(location, data);
pendingPoints.add(data);
}
}
@Override
public void onSampledOutTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) {
// Still account for the point in the smoothing buffers.
fillDataPoint(location, null);
}
}
@Override
public void onSegmentSplit() {
// Do nothing.
}
@Override
public void onNewTrackPointsDone() {
chartView.addDataPoints(pendingPoints);
pendingPoints.clear();
getActivity().runOnUiThread(updateChart);
}
@Override
public void clearWaypoints() {
chartView.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
chartView.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
getActivity().runOnUiThread(updateChart);
}
@Override
public boolean onUnitsChanged(boolean metric) {
if (metricUnits == metric) {
return false;
}
metricUnits = metric;
chartView.setMetricUnits(metricUnits);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.requestLayout();
}
});
return true;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (reportSpeed == speed) {
return false;
}
reportSpeed = speed;
chartView.setReportSpeed(speed, getActivity());
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
chartView.requestLayout();
}
});
return true;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if recording. Needs to be synchronized because trackDataHub
* can be accessed by multiple threads.
*/
private synchronized boolean isRecording() {
return trackDataHub != null && trackDataHub.isRecordingSelected();
}
/**
* Reloads the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void reloadTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.reloadDataForListener(this);
}
}
/**
* To zoom in.
*/
private void zoomIn() {
chartView.zoomIn();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* To zoom out.
*/
private void zoomOut() {
chartView.zoomOut();
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
/**
* Given a location, fill in a data point, an array of double[6]. <br>
* data[0] = time/distance <br>
* data[1] = elevation <br>
* data[2] = speed <br>
* data[3] = power <br>
* data[4] = cadence <br>
* data[5] = heart rate <br>
*
* @param location the location
* @param data the data point to fill in, can be null
*/
@VisibleForTesting
void fillDataPoint(Location location, double data[]) {
double timeOrDistance = Double.NaN;
double elevation = Double.NaN;
double speed = Double.NaN;
double power = Double.NaN;
double cadence = Double.NaN;
double heartRate = Double.NaN;
// TODO: Use TripStatisticsBuilder
if (chartView.getMode() == Mode.BY_DISTANCE) {
if (lastLocation != null) {
double distance = lastLocation.distanceTo(location) * UnitConversions.M_TO_KM;
if (metricUnits) {
totalDistance += distance;
} else {
totalDistance += distance * UnitConversions.KM_TO_MI;
}
}
timeOrDistance = totalDistance;
} else {
if (startTime == -1L) {
startTime = location.getTime();
}
timeOrDistance = location.getTime() - startTime;
}
elevationBuffer.setNext(metricUnits ? location.getAltitude() : location.getAltitude()
* UnitConversions.M_TO_FT);
elevation = elevationBuffer.getAverage();
if (lastLocation == null) {
if (Math.abs(location.getSpeed() - INVALID_SPEED) > 1) {
speedBuffer.setNext(location.getSpeed());
}
} else if (TripStatisticsBuilder.isValidSpeed(location.getTime(), location.getSpeed(),
lastLocation.getTime(), lastLocation.getSpeed(), speedBuffer)
&& (location.getSpeed() <= trackMaxSpeed)) {
speedBuffer.setNext(location.getSpeed());
}
speed = speedBuffer.getAverage() * UnitConversions.MS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
if (!reportSpeed) {
speed = speed == 0 ? 0.0 : 60.0 / speed;
}
if (location instanceof MyTracksLocation
&& ((MyTracksLocation) location).getSensorDataSet() != null) {
SensorDataSet sensorDataSet = ((MyTracksLocation) location).getSensorDataSet();
if (sensorDataSet.hasPower() && sensorDataSet.getPower().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getPower().hasValue()) {
power = sensorDataSet.getPower().getValue();
}
if (sensorDataSet.hasCadence()
&& sensorDataSet.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getCadence().hasValue()) {
cadence = sensorDataSet.getCadence().getValue();
}
if (sensorDataSet.hasHeartRate()
&& sensorDataSet.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorDataSet.getHeartRate().hasValue()) {
heartRate = sensorDataSet.getHeartRate().getValue();
}
}
if (data != null) {
data[0] = timeOrDistance;
data[1] = elevation;
data[2] = speed;
data[3] = power;
data[4] = cadence;
data[5] = heartRate;
}
lastLocation = location;
}
@VisibleForTesting
ChartView getChartView() {
return chartView;
}
@VisibleForTesting
void setChartView(ChartView view) {
chartView = view;
}
@VisibleForTesting
void setTrackMaxSpeed(double value) {
trackMaxSpeed = value;
}
@VisibleForTesting
void setMetricUnits(boolean value) {
metricUnits = value;
}
@VisibleForTesting
void setReportSpeed(boolean value) {
reportSpeed = value;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.fragments.AboutDialogFragment;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.FragmentActivity;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
/**
* An activity that displays the help page.
*
* @author Sandor Dornbush
*/
public class HelpActivity extends FragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this);
setContentView(R.layout.help);
findViewById(R.id.help_ok).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
finish();
}
});
findViewById(R.id.help_about).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
new AboutDialogFragment().show(
getSupportFragmentManager(), AboutDialogFragment.ABOUT_DIALOG_TAG);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() != android.R.id.home) {
return false;
}
startActivity(new Intent(this, TrackListActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK));
return true;
}
}
@@ -142,7 +142,7 @@ public class ImportActivity extends Activity {
if (!importAll && trackId != -1L) {
Intent intent = new Intent(ImportActivity.this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(TrackDetailActivity.TRACK_ID, trackId);
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
}
finish();
@@ -1,558 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.EnumSet;
/**
* The map view activity of the MyTracks application.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class MapActivity extends com.google.android.maps.MapActivity
implements View.OnTouchListener, View.OnClickListener,
TrackDataListener {
// Saved instance state keys:
// ---------------------------
private static final String KEY_CURRENT_LOCATION = "currentLocation";
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
private TrackDataHub dataHub;
/**
* True if the map should be scrolled so that the pointer is always in the
* visible area.
*/
private boolean keepMyLocationVisible;
/**
* True if we've already zoomed into the current location.
* Only relevant when {@link #keepMyLocationVisible} is true.
*/
private boolean myLocationWasZoomedIn;
/**
* The ID of a track on which we want to show a waypoint.
* The waypoint will be shown as soon as the track is loaded.
*/
private long showWaypointTrackId;
/**
* The ID of a waypoint which we want to show.
* The waypoint will be shown as soon as its track is loaded.
*/
private long showWaypointId;
/**
* The track that's currently selected.
* This differs from {@link TrackDataHub#getSelectedTrackId} in that this one is only set after
* actual track data has been received.
*/
private long selectedTrackId;
/**
* The current pointer location.
* This is kept to quickly center on it when the user requests.
*/
private Location currentLocation;
// UI elements:
// -------------
private RelativeLayout screen;
private MapView mapView;
private MapOverlay mapOverlay;
private LinearLayout messagePane;
private TextView messageText;
private LinearLayout busyPane;
/**
* We are not displaying driving directions. Just an arbitrary track that is
* not associated to any licensed mapping data. Therefore it should be okay to
* return false here and still comply with the terms of service.
*/
@Override
protected boolean isRouteDisplayed() {
return false;
}
/**
* We are displaying a location. This needs to return true in order to comply
* with the terms of service.
*/
@Override
protected boolean isLocationDisplayed() {
return true;
}
// Application life cycle:
// ------------------------
@Override
protected void onCreate(Bundle bundle) {
Log.d(TAG, "MapActivity.onCreate");
super.onCreate(bundle);
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
// Inflate the layout:
setContentView(R.layout.mytracks_layout);
// Remove the window's background because the MapView will obscure it
getWindow().setBackgroundDrawable(null);
// Set up a map overlay:
screen = (RelativeLayout) findViewById(R.id.screen);
mapView = (MapView) findViewById(R.id.map);
mapView.requestFocus();
mapOverlay = new MapOverlay(this);
mapView.getOverlays().add(mapOverlay);
mapView.setOnTouchListener(this);
mapView.setBuiltInZoomControls(true);
messagePane = (LinearLayout) findViewById(R.id.messagepane);
messageText = (TextView) findViewById(R.id.messagetext);
busyPane = (LinearLayout) findViewById(R.id.busypane);
}
@Override
protected void onRestoreInstanceState(Bundle bundle) {
Log.d(TAG, "MapActivity.onRestoreInstanceState");
if (bundle != null) {
super.onRestoreInstanceState(bundle);
keepMyLocationVisible =
bundle.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
if (bundle.containsKey(KEY_CURRENT_LOCATION)) {
currentLocation = (Location) bundle.getParcelable(KEY_CURRENT_LOCATION);
if (currentLocation != null) {
showCurrentLocation();
}
} else {
currentLocation = null;
}
}
}
@Override
protected void onResume() {
Log.d(TAG, "MapActivity.onResume");
super.onResume();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.POINT_UPDATES,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.COMPASS_UPDATES));
}
@Override
protected void onSaveInstanceState(Bundle outState) {
Log.d(TAG, "MapActivity.onSaveInstanceState");
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
if (currentLocation != null) {
outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
}
super.onSaveInstanceState(outState);
}
@Override
protected void onPause() {
Log.d(TAG, "MapActivity.onPause");
dataHub.unregisterTrackDataListener(this);
dataHub = null;
super.onPause();
}
/**
* Tests if a location is visible.
*
* @param location a given location
* @return true if the given location is within the visible map area
*/
private boolean locationIsVisible(Location location) {
if (location == null || mapView == null) {
return false;
}
GeoPoint center = mapView.getMapCenter();
int latSpan = mapView.getLatitudeSpan();
int lonSpan = mapView.getLongitudeSpan();
// Bottom of map view is obscured by zoom controls/buttons.
// Subtract a margin from the visible area:
GeoPoint marginBottom = mapView.getProjection().fromPixels(
0, mapView.getHeight());
GeoPoint marginTop = mapView.getProjection().fromPixels(0,
mapView.getHeight()
- mapView.getZoomButtonsController().getZoomControls().getHeight());
int margin =
Math.abs(marginTop.getLatitudeE6() - marginBottom.getLatitudeE6());
GeoRect r = new GeoRect(center, latSpan, lonSpan);
r.top += margin;
GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
return r.contains(geoPoint);
}
/**
* Centers (and keeps centered) the map on the current location.
*/
public void showMyLocation() {
dataHub.forceUpdateLocation();
keepMyLocationVisible = true;
myLocationWasZoomedIn = false;
if (currentLocation != null) {
showCurrentLocation();
}
}
/**
* Moves the location pointer to the current location and center the map if
* the current location is outside the visible area.
*/
private void showCurrentLocation() {
if (mapOverlay == null || mapView == null) {
return;
}
mapOverlay.setMyLocation(currentLocation);
mapView.postInvalidate();
if (currentLocation != null && keepMyLocationVisible && !locationIsVisible(currentLocation)) {
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
MapController controller = mapView.getController();
controller.animateTo(geoPoint);
if (!myLocationWasZoomedIn && mapView.getZoomLevel() < 18) {
// Only zoom in the first time we show the location.
myLocationWasZoomedIn = true;
controller.setZoom(18);
}
}
}
@Override
public void onTrackUpdated(Track track) {
// We don't care.
}
/**
* Zooms and pans the map so that the given track is visible.
*
* @param track the track
*/
private void zoomMapToBoundaries(Track track) {
if (mapView == null) {
return;
}
if (track == null || track.getNumberOfPoints() < 2) {
return;
}
TripStatistics stats = track.getStatistics();
int bottom = stats.getBottom();
int left = stats.getLeft();
int latSpanE6 = stats.getTop() - bottom;
int lonSpanE6 = stats.getRight() - left;
if (latSpanE6 > 0
&& latSpanE6 < 180E6
&& lonSpanE6 > 0
&& lonSpanE6 < 360E6) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint(
bottom + latSpanE6 / 2,
left + lonSpanE6 / 2);
if (LocationUtils.isValidGeoPoint(center)) {
mapView.getController().setCenter(center);
mapView.getController().zoomToSpan(latSpanE6, lonSpanE6);
}
}
}
/**
* Zooms and pans the map so that the given waypoint is visible.
*/
public void showWaypoint(long waypointId) {
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(this);
Waypoint wpt = providerUtils.getWaypoint(waypointId);
if (wpt != null && wpt.getLocation() != null) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint(
(int) (wpt.getLocation().getLatitude() * 1E6),
(int) (wpt.getLocation().getLongitude() * 1E6));
mapView.getController().setCenter(center);
mapView.getController().setZoom(20);
mapView.invalidate();
}
}
/**
* Zooms and pans the map so that the given waypoint is visible, when the given track is loaded.
* If the track is already loaded, it does that immediately.
*
* @param trackId the ID of the track on which to show the waypoint
* @param waypointId the ID of the waypoint to show
*/
public void showWaypoint(long trackId, long waypointId) {
synchronized (this) {
if (trackId == selectedTrackId) {
showWaypoint(waypointId);
return;
}
showWaypointTrackId = trackId;
showWaypointId = waypointId;
}
}
/**
* Does the proper zooming/panning for a just-loaded track.
* This may be either zooming to a waypoint that has been previously selected, or
* zooming to the whole track.
*
* @param track the loaded track
*/
private void zoomLoadedTrack(Track track) {
synchronized (this) {
if (track.getId() == showWaypointTrackId) {
// There's a waypoint to show in this track.
showWaypoint(showWaypointId);
showWaypointId = 0L;
showWaypointTrackId = 0L;
} else {
// Zoom out to show the whole track.
zoomMapToBoundaries(track);
}
}
}
@Override
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
runOnUiThread(new Runnable() {
@Override
public void run() {
boolean trackSelected = track != null;
mapOverlay.setTrackDrawingEnabled(trackSelected);
if (trackSelected) {
busyPane.setVisibility(View.VISIBLE);
synchronized (this) {
// Need to get the track ID only at this point, to prevent a race condition
// among showWaypoint, zoomLoadedTrack and dataHub.loadTrack.
selectedTrackId = track.getId();
zoomLoadedTrack(track);
}
mapOverlay.setShowEndMarker(!isRecording);
busyPane.setVisibility(View.GONE);
}
mapView.invalidate();
}
});
}
/**
* Returns whether the map is currently in satellite view mode.
*/
public boolean isSatelliteView() {
return mapView.isSatellite();
}
/**
* Changes whether the map should be in satellite view mode.
*/
public void setSatelliteView(boolean sat) {
mapView.setSatellite(sat);
}
@Override
public void onClick(View v) {
if (v == messagePane) {
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
}
/**
* We want the pointer to become visible again in case of the next location
* update:
*/
@Override
public boolean onTouch(View view, MotionEvent event) {
if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!locationIsVisible(currentLocation)) {
keepMyLocationVisible = false;
}
}
return false;
}
@Override
public void onProviderStateChange(ProviderState state) {
final int messageId;
final boolean isGpsDisabled;
switch (state) {
case DISABLED:
messageId = R.string.gps_need_to_enable;
isGpsDisabled = true;
break;
case NO_FIX:
case BAD_FIX:
messageId = R.string.gps_wait_for_fix;
isGpsDisabled = false;
break;
case GOOD_FIX:
// Nothing to show.
messageId = -1;
isGpsDisabled = false;
break;
default:
throw new IllegalArgumentException("Unexpected state: " + state);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
if (messageId != -1) {
messageText.setText(messageId);
messagePane.setVisibility(View.VISIBLE);
if (isGpsDisabled) {
// Give a warning about this state.
Toast.makeText(MapActivity.this,
R.string.gps_not_found,
Toast.LENGTH_LONG).show();
// Make clicking take the user to the location settings.
messagePane.setOnClickListener(MapActivity.this);
} else {
messagePane.setOnClickListener(null);
}
} else {
messagePane.setVisibility(View.GONE);
}
screen.requestLayout();
}
});
}
@Override
public void onCurrentLocationChanged(Location location) {
currentLocation = location;
showCurrentLocation();
}
@Override
public void onCurrentHeadingChanged(double heading) {
synchronized (this) {
if (mapOverlay.setHeading((float) heading)) {
mapView.postInvalidate();
}
}
}
@Override
public void clearWaypoints() {
mapOverlay.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (LocationUtils.isValidLocation(waypoint.getLocation())) {
// TODO: Optimize locking inside addWaypoint
mapOverlay.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
mapView.postInvalidate();
}
@Override
public void clearTrackPoints() {
mapOverlay.clearPoints();
}
@Override
public void onNewTrackPoint(Location loc) {
mapOverlay.addLocation(loc);
}
@Override
public void onSegmentSplit() {
mapOverlay.addSegmentSplit();
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
mapView.postInvalidate();
}
@Override
public boolean onUnitsChanged(boolean metric) {
// We don't care.
return false;
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
// We don't care.
return false;
}
}
@@ -0,0 +1,520 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.GeoRect;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.location.Location;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.util.EnumSet;
/**
* A fragment to display map to the user.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class MapFragment extends Fragment
implements View.OnTouchListener, View.OnClickListener, TrackDataListener {
private static final String KEY_CURRENT_LOCATION = "currentLocation";
private static final String KEY_KEEP_MY_LOCATION_VISIBLE = "keepMyLocationVisible";
private TrackDataHub trackDataHub;
// True to keep my location visible.
private boolean keepMyLocationVisible;
// True to zoom to my location. Only apply when keepMyLocationVisible is true.
private boolean zoomToMyLocation;
// The track id of the waypoint to show.
private long waypointTrackId;
// The waypoint id to show
private long waypointId;
// The current selected track id. Set in onSelectedTrackChanged.
private long currentSelectedTrackId;
// The current location. Set in onCurrentLocationChanged.
private Location currentLocation;
// UI elements
private View mapViewContainer;
private MapOverlay mapOverlay;
private RelativeLayout screen;
private MapView mapView;
private LinearLayout messagePane;
private TextView messageText;
private LinearLayout busyPane;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mapViewContainer = ((TrackDetailActivity) getActivity()).getMapViewContainer();
mapOverlay = new MapOverlay(getActivity());
screen = (RelativeLayout) mapViewContainer.findViewById(R.id.screen);
mapView = (MapView) mapViewContainer.findViewById(R.id.map);
mapView.requestFocus();
mapView.setOnTouchListener(this);
mapView.setBuiltInZoomControls(true);
mapView.getOverlays().add(mapOverlay);
messagePane = (LinearLayout) mapViewContainer.findViewById(R.id.messagepane);
messageText = (TextView) mapViewContainer.findViewById(R.id.messagetext);
busyPane = (LinearLayout) mapViewContainer.findViewById(R.id.busypane);
return mapViewContainer;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
keepMyLocationVisible = savedInstanceState.getBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, false);
currentLocation = (Location) savedInstanceState.getParcelable(KEY_CURRENT_LOCATION);
if (currentLocation != null) {
updateCurrentLocation();
}
}
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_KEEP_MY_LOCATION_VISIBLE, keepMyLocationVisible);
if (currentLocation != null) {
outState.putParcelable(KEY_CURRENT_LOCATION, currentLocation);
}
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
}
@Override
public void onDestroyView() {
super.onDestroyView();
ViewGroup parentViewGroup = (ViewGroup) mapViewContainer.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeView(mapViewContainer);
}
}
/**
* Shows my location.
*/
public void showMyLocation() {
updateTrackDataHub();
keepMyLocationVisible = true;
zoomToMyLocation = true;
if (currentLocation != null) {
updateCurrentLocation();
}
}
/**
* Shows the waypoint.
*
* @param id the waypoint id
*/
public void showWaypoint(long id) {
MyTracksProviderUtils MyTracksProviderUtils = Factory.get(getActivity());
Waypoint waypoint = MyTracksProviderUtils.getWaypoint(id);
if (waypoint != null && waypoint.getLocation() != null) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint((int) (waypoint.getLocation().getLatitude() * 1E6),
(int) (waypoint.getLocation().getLongitude() * 1E6));
mapView.getController().setCenter(center);
mapView.getController().setZoom(mapView.getMaxZoomLevel());
mapView.invalidate();
}
}
/**
* Shows the waypoint.
*
* @param trackId the track id
* @param id the waypoint id
*/
public void showWaypoint(long trackId, long id) {
/*
* Synchronize to prevent race condition in changing waypointTrackId and
* waypointId variables.
*/
synchronized (this) {
if (trackId == currentSelectedTrackId) {
showWaypoint(id);
waypointTrackId = -1L;
waypointId = -1L;
return;
}
waypointTrackId = trackId;
waypointId = id;
}
}
/**
* Returns true if in satellite mode.
*/
public boolean isSatelliteView() {
return mapView.isSatellite();
}
/**
* Sets the satellite mode
*
* @param enabled true for satellite mode, false for map mode
*/
public void setSatelliteView(boolean enabled) {
mapView.setSatellite(enabled);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (keepMyLocationVisible && event.getAction() == MotionEvent.ACTION_MOVE) {
if (!isVisible(currentLocation)) {
/*
* Only set to false when no longer visible. Thus can keep showing the
* current location with the next location update.
*/
keepMyLocationVisible = false;
}
}
return false;
}
@Override
public void onClick(View v) {
if (v == messagePane) {
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
}
@Override
public void onProviderStateChange(ProviderState state) {
final int messageId;
final boolean isGpsDisabled;
switch (state) {
case DISABLED:
messageId = R.string.gps_need_to_enable;
isGpsDisabled = true;
break;
case NO_FIX:
case BAD_FIX:
messageId = R.string.gps_wait_for_fix;
isGpsDisabled = false;
break;
case GOOD_FIX:
messageId = -1;
isGpsDisabled = false;
break;
default:
throw new IllegalArgumentException("Unexpected state: " + state);
}
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (messageId != -1) {
messageText.setText(messageId);
messagePane.setVisibility(View.VISIBLE);
if (isGpsDisabled) {
Toast.makeText(getActivity(), R.string.gps_not_found, Toast.LENGTH_LONG).show();
// Click to show the location source settings
messagePane.setOnClickListener(MapFragment.this);
} else {
messagePane.setOnClickListener(null);
}
} else {
messagePane.setVisibility(View.GONE);
}
screen.requestLayout();
}
});
}
@Override
public void onCurrentLocationChanged(Location location) {
currentLocation = location;
updateCurrentLocation();
}
@Override
public void onCurrentHeadingChanged(double heading) {
if (mapOverlay.setHeading((float) heading)) {
mapView.postInvalidate();
}
}
@Override
public void onSelectedTrackChanged(final Track track, final boolean isRecording) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
boolean hasTrack = track != null;
mapOverlay.setTrackDrawingEnabled(hasTrack);
if (hasTrack) {
busyPane.setVisibility(View.VISIBLE);
synchronized (this) {
/*
* Synchronize to prevent race condition in changing waypointTrackId
* and waypointId variables.
*/
currentSelectedTrackId = track.getId();
updateMap(track);
}
mapOverlay.setShowEndMarker(!isRecording);
busyPane.setVisibility(View.GONE);
}
mapView.invalidate();
}
});
}
@Override
public void onTrackUpdated(Track track) {
// We don't care.
}
@Override
public void clearTrackPoints() {
mapOverlay.clearPoints();
}
@Override
public void onNewTrackPoint(Location location) {
if (LocationUtils.isValidLocation(location)) {
mapOverlay.addLocation(location);
}
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSegmentSplit() {
mapOverlay.addSegmentSplit();
}
@Override
public void onNewTrackPointsDone() {
mapView.postInvalidate();
}
@Override
public void clearWaypoints() {
mapOverlay.clearWaypoints();
}
@Override
public void onNewWaypoint(Waypoint waypoint) {
if (waypoint != null && LocationUtils.isValidLocation(waypoint.getLocation())) {
// TODO: Optimize locking inside addWaypoint
mapOverlay.addWaypoint(waypoint);
}
}
@Override
public void onNewWaypointsDone() {
mapView.postInvalidate();
}
@Override
public boolean onUnitsChanged(boolean metric) {
// We don't care.
return false;
}
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
// We don't care.
return false;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.WAYPOINT_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.COMPASS_UPDATES));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Updates the trackDataHub. Needs to be synchronized because trackDataHub can be
* accessed by multiple threads.
*/
private synchronized void updateTrackDataHub() {
if (trackDataHub != null) {
trackDataHub.forceUpdateLocation();
}
}
/**
* Updates the map by either zooming to the requested waypoint or showing the track.
*
* @param track the track
*/
private void updateMap(Track track) {
if (track.getId() == waypointTrackId) {
// Show the waypoint
showWaypoint(waypointId);
waypointTrackId = -1L;
waypointId = -1L;
} else {
// Show the track
showTrack(track);
}
}
/**
* Returns true if the location is visible.
*
* @param location the location
*/
private boolean isVisible(Location location) {
if (location == null || mapView == null) {
return false;
}
GeoPoint mapCenter = mapView.getMapCenter();
int latitudeSpan = mapView.getLatitudeSpan();
int longitudeSpan = mapView.getLongitudeSpan();
/*
* The bottom of the mapView is obscured by the zoom controls, subtract its
* height from the visible area.
*/
GeoPoint zoomControlBottom = mapView.getProjection().fromPixels(0, mapView.getHeight());
GeoPoint zoomControlTop = mapView.getProjection().fromPixels(
0, mapView.getHeight() - mapView.getZoomButtonsController().getZoomControls().getHeight());
int zoomControlMargin = Math.abs(zoomControlTop.getLatitudeE6()
- zoomControlBottom.getLatitudeE6());
GeoRect geoRect = new GeoRect(mapCenter, latitudeSpan, longitudeSpan);
geoRect.top += zoomControlMargin;
GeoPoint geoPoint = LocationUtils.getGeoPoint(location);
return geoRect.contains(geoPoint);
}
/**
* Updates the current location and centers it if necessary.
*/
private void updateCurrentLocation() {
if (mapOverlay == null || mapView == null) {
return;
}
mapOverlay.setMyLocation(currentLocation);
mapView.postInvalidate();
if (currentLocation != null && keepMyLocationVisible && !isVisible(currentLocation)) {
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
MapController mapController = mapView.getController();
mapController.animateTo(geoPoint);
if (zoomToMyLocation) {
// Only zoom in the first time we show the location.
zoomToMyLocation = false;
if (mapView.getZoomLevel() < mapView.getMaxZoomLevel()) {
mapController.setZoom(mapView.getMaxZoomLevel());
}
}
}
}
/**
* Shows the track.
*
* @param track the track
*/
private void showTrack(Track track) {
if (mapView == null || track == null || track.getNumberOfPoints() < 2) {
return;
}
TripStatistics tripStatistics = track.getStatistics();
int bottom = tripStatistics.getBottom();
int left = tripStatistics.getLeft();
int latitudeSpanE6 = tripStatistics.getTop() - bottom;
int longitudeSpanE6 = tripStatistics.getRight() - left;
if (latitudeSpanE6 > 0 && latitudeSpanE6 < 180E6 && longitudeSpanE6 > 0
&& longitudeSpanE6 < 360E6) {
keepMyLocationVisible = false;
GeoPoint center = new GeoPoint(bottom + latitudeSpanE6 / 2, left + longitudeSpanE6 / 2);
if (LocationUtils.isValidGeoPoint(center)) {
mapView.getController().setCenter(center);
mapView.getController().zoomToSpan(latitudeSpanE6, longitudeSpanE6);
}
}
}
}
@@ -1,214 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
/**
* Creates previous and next arrows for a given activity.
*
* @author Leif Hendrik Wilden
*/
public class NavControls {
private static final int KEEP_VISIBLE_MILLIS = 4000;
private static final boolean FADE_CONTROLS = true;
private static final Animation SHOW_NEXT_ANIMATION =
new AlphaAnimation(0F, 1F);
private static final Animation HIDE_NEXT_ANIMATION =
new AlphaAnimation(1F, 0F);
private static final Animation SHOW_PREV_ANIMATION =
new AlphaAnimation(0F, 1F);
private static final Animation HIDE_PREV_ANIMATION =
new AlphaAnimation(1F, 0F);
/**
* A touchable image view.
* When touched it changes the navigation control icons accordingly.
*/
private class TouchLayout extends RelativeLayout implements Runnable {
private final boolean isLeft;
private final ImageView icon;
public TouchLayout(Context context, boolean isLeft) {
super(context);
this.isLeft = isLeft;
this.icon = new ImageView(context);
icon.setVisibility(View.GONE);
addView(icon);
}
public void setIcon(Drawable drawable) {
icon.setImageDrawable(drawable);
icon.setVisibility(View.VISIBLE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
setPressed(true);
shiftIcons(isLeft);
// Call the user back
handler.post(this);
break;
case MotionEvent.ACTION_UP:
setPressed(false);
break;
}
return super.onTouchEvent(event);
}
@Override
public void run() {
touchRunnable.run();
setPressed(false);
}
}
private final Handler handler = new Handler();
private final Runnable dismissControls = new Runnable() {
public void run() {
hide();
}
};
private final TouchLayout prevImage;
private final TouchLayout nextImage;
private final TypedArray leftIcons;
private final TypedArray rightIcons;
private final Runnable touchRunnable;
private boolean isVisible = false;
private int currentIcons;
public NavControls(Context context, ViewGroup container,
TypedArray leftIcons, TypedArray rightIcons,
Runnable touchRunnable) {
this.leftIcons = leftIcons;
this.rightIcons = rightIcons;
this.touchRunnable = touchRunnable;
if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
throw new IllegalArgumentException("Invalid icons specified");
}
if (touchRunnable == null) {
throw new NullPointerException("Runnable cannot be null");
}
LayoutParams prevParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
LayoutParams nextParams = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
prevParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
nextParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
prevParams.addRule(RelativeLayout.CENTER_VERTICAL);
nextParams.addRule(RelativeLayout.CENTER_VERTICAL);
nextImage = new TouchLayout(context, false);
prevImage = new TouchLayout(context, true);
nextImage.setLayoutParams(nextParams);
prevImage.setLayoutParams(prevParams);
nextImage.setVisibility(View.INVISIBLE);
prevImage.setVisibility(View.INVISIBLE);
container.addView(prevImage);
container.addView(nextImage);
prevImage.setIcon(leftIcons.getDrawable(0));
nextImage.setIcon(rightIcons.getDrawable(0));
this.currentIcons = 0;
}
private void keepVisible() {
if (isVisible && FADE_CONTROLS) {
handler.removeCallbacks(dismissControls);
handler.postDelayed(dismissControls, KEEP_VISIBLE_MILLIS);
}
}
public void show() {
if (!isVisible) {
SHOW_PREV_ANIMATION.setDuration(500);
SHOW_PREV_ANIMATION.startNow();
prevImage.setPressed(false);
prevImage.setAnimation(SHOW_PREV_ANIMATION);
prevImage.setVisibility(View.VISIBLE);
SHOW_NEXT_ANIMATION.setDuration(500);
SHOW_NEXT_ANIMATION.startNow();
nextImage.setPressed(false);
nextImage.setAnimation(SHOW_NEXT_ANIMATION);
nextImage.setVisibility(View.VISIBLE);
isVisible = true;
keepVisible();
} else {
keepVisible();
}
}
public void hideNow() {
handler.removeCallbacks(dismissControls);
isVisible = false;
prevImage.clearAnimation();
prevImage.setVisibility(View.INVISIBLE);
nextImage.clearAnimation();
nextImage.setVisibility(View.INVISIBLE);
}
public void hide() {
isVisible = false;
prevImage.setAnimation(HIDE_PREV_ANIMATION);
HIDE_PREV_ANIMATION.setDuration(500);
HIDE_PREV_ANIMATION.startNow();
prevImage.setVisibility(View.INVISIBLE);
nextImage.setAnimation(HIDE_NEXT_ANIMATION);
HIDE_NEXT_ANIMATION.setDuration(500);
HIDE_NEXT_ANIMATION.startNow();
nextImage.setVisibility(View.INVISIBLE);
}
public int getCurrentIcons() {
return currentIcons;
}
private void shiftIcons(boolean isLeft) {
// Increment or decrement by one, with wrap around
currentIcons = (currentIcons + leftIcons.length() +
(isLeft ? -1 : 1)) % leftIcons.length();
prevImage.setIcon(leftIcons.getDrawable(currentIcons));
nextImage.setIcon(rightIcons.getDrawable(currentIcons));
}
}
@@ -65,7 +65,7 @@ public class SearchActivity extends ListActivity {
private static final String TRACK_ID_FIELD = "trackId";
private static final String WAYPOINT_ID_FIELD = "waypointId";
private static final String EXTRA_CURRENT_TRACK_ID = "trackId";
public static final String EXTRA_TRACK_ID = "track_id";
private static final boolean LOG_SCORES = true;
@@ -116,7 +116,7 @@ public class SearchActivity extends ListActivity {
String textQuery = intent.getStringExtra(SearchManager.QUERY);
Location currentLocation = locationManager.getLastKnownLocation("gps");
long currentTrackId = intent.getLongExtra(EXTRA_CURRENT_TRACK_ID, -1);
long currentTrackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
long currentTimestamp = System.currentTimeMillis();
final SearchQuery query =
@@ -249,9 +249,9 @@ public class SearchActivity extends ListActivity {
private Intent createViewDataIntent(Map<String, Object> clickedData) {
Intent intent = new Intent(this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(TrackDetailActivity.TRACK_ID, (Long) clickedData.get(TRACK_ID_FIELD));
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, (Long) clickedData.get(TRACK_ID_FIELD));
if (clickedData.containsKey(WAYPOINT_ID_FIELD)) {
intent.putExtra(TrackDetailActivity.WAYPOINT_ID, (Long) clickedData.get(WAYPOINT_ID_FIELD));
intent.putExtra(TrackDetailActivity.EXTRA_WAYPOINT_ID, (Long) clickedData.get(WAYPOINT_ID_FIELD));
}
return intent;
}
@@ -1,320 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.ScrollView;
import android.widget.TextView;
import java.util.EnumSet;
/**
* An activity that displays track statistics to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatsActivity extends Activity implements TrackDataListener {
/**
* A runnable for posting to the UI thread. Will update the total time field.
*/
private final Runnable updateResults = new Runnable() {
public void run() {
if (dataHub != null && dataHub.isRecordingSelected()) {
utils.setTime(R.id.total_time_register,
System.currentTimeMillis() - startTime);
}
}
};
private StatsUtilities utils;
private UIUpdateThread thread;
/**
* The start time of the selected track.
*/
private long startTime = -1;
private TrackDataHub dataHub;
private SharedPreferences preferences;
/**
* A thread that updates the total time field every second.
*/
private class UIUpdateThread extends Thread {
public UIUpdateThread() {
super();
Log.i(TAG, "Created UI update thread");
}
@Override
public void run() {
Log.i(TAG, "Started UI update thread");
while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
runOnUiThread(updateResults);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
Log.w(TAG, "StatsActivity: Caught exception on sleep.", e);
break;
}
}
Log.w(TAG, "UIUpdateThread finished.");
}
}
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
utils = new StatsUtilities(this);
// The volume we want to control is the Text-To-Speech volume
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
setContentView(R.layout.stats);
ScrollView sv = ((ScrollView) findViewById(R.id.scrolly));
sv.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
showUnknownLocation();
updateLabels();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
if (metrics.heightPixels > 600) {
((TextView) findViewById(R.id.speed_register)).setTextSize(80.0f);
}
}
@Override
protected void onResume() {
super.onResume();
dataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
}
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
dataHub = null;
if (thread != null) {
thread.interrupt();
thread = null;
}
super.onStop();
}
@Override
public boolean onUnitsChanged(boolean metric) {
if (utils.isMetricUnits() == metric) {
return false;
}
utils.setMetricUnits(metric);
updateLabels();
return true;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (utils.isReportSpeed() == speed) {
return false;
}
utils.setReportSpeed(speed);
updateLabels();
return true;
}
private void updateLabels() {
runOnUiThread(new Runnable() {
@Override
public void run() {
utils.updateUnits();
utils.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
utils.setSpeedLabels();
}
});
}
/**
* Updates the given location fields (latitude, longitude, altitude) and all
* other fields.
*
* @param l may be null (will set location fields to unknown)
*/
private void showLocation(Location l) {
utils.setAltitude(R.id.elevation_register, l.getAltitude());
utils.setLatLong(R.id.latitude_register, l.getLatitude());
utils.setLatLong(R.id.longitude_register, l.getLongitude());
utils.setSpeed(R.id.speed_register, l.getSpeed() * UnitConversions.MS_TO_KMH);
}
private void showUnknownLocation() {
utils.setUnknown(R.id.elevation_register);
utils.setUnknown(R.id.latitude_register);
utils.setUnknown(R.id.longitude_register);
utils.setUnknown(R.id.speed_register);
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
/*
* Checks if this activity needs to update live track data or not.
* If so, make sure that:
* a) a thread keeps updating the total time
* b) a location listener is registered
* c) a content observer is registered
* Otherwise unregister listeners, observers, and kill update thread.
*/
final boolean startThread = (thread == null) && isRecording;
final boolean killThread = (thread != null) && (!isRecording);
if (startThread) {
thread = new UIUpdateThread();
thread.start();
} else if (killThread) {
thread.interrupt();
thread = null;
}
}
@Override
public void onCurrentLocationChanged(final Location loc) {
TrackDataHub localDataHub = dataHub;
if (localDataHub != null && localDataHub.isRecordingSelected()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (loc != null) {
showLocation(loc);
} else {
showUnknownLocation();
}
}
});
}
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onProviderStateChange(ProviderState state) {
switch (state) {
case DISABLED:
case NO_FIX:
runOnUiThread(new Runnable() {
@Override
public void run() {
showUnknownLocation();
}
});
break;
}
}
@Override
public void onTrackUpdated(final Track track) {
TrackDataHub localDataHub = dataHub;
final boolean recordingSelected = localDataHub != null && localDataHub.isRecordingSelected();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (track == null || track.getStatistics() == null) {
utils.setAllToUnknown();
return;
}
startTime = track.getStatistics().getStartTime();
if (!recordingSelected) {
utils.setTime(R.id.total_time_register,
track.getStatistics().getTotalTime());
showUnknownLocation();
}
utils.setAllStats(track.getStatistics());
}
});
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
@Override
public void clearTrackPoints() {
// We don't care.
}
@Override
public void onNewTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSegmentSplit() {
// We don't care.
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
// We don't care.
}
}
@@ -0,0 +1,322 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import java.util.EnumSet;
/**
* A fragment to display track statistics to the user.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public class StatsFragment extends Fragment implements TrackDataListener {
private static final String TAG = StatsFragment.class.getSimpleName();
private SharedPreferences sharedPreferences;
private StatsUtilities statsUtilities;
private TrackDataHub trackDataHub;
private UiUpdateThread uiUpdateThread;
// The start time of the current track.
private long startTime = -1L;
// A runnable to update the total time field.
private final Runnable updateTotalTime = new Runnable() {
public void run() {
if (isRecording()) {
statsUtilities.setTime(R.id.total_time_register, System.currentTimeMillis() - startTime);
}
}
};
/**
* A thread that updates the total time field every second.
*/
private class UiUpdateThread extends Thread {
@Override
public void run() {
Log.d(TAG, "UI update thread started");
while (ServiceUtils.isRecording(getActivity(), null, sharedPreferences)) {
getActivity().runOnUiThread(updateTotalTime);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
Log.d(TAG, "UI update thread caught exception", e);
break;
}
}
Log.d(TAG, "UI update thread finished");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedPreferences = getActivity()
.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
statsUtilities = new StatsUtilities(getActivity());
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.stats, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ScrollView scrollView = (ScrollView) getActivity().findViewById(R.id.scrolly);
scrollView.setScrollBarStyle(ScrollView.SCROLLBARS_OUTSIDE_INSET);
updateLabels();
setLocationUnknown();
}
@Override
public void onResume() {
super.onResume();
resumeTrackDataHub();
}
@Override
public void onPause() {
super.onPause();
pauseTrackDataHub();
if (uiUpdateThread != null) {
uiUpdateThread.interrupt();
uiUpdateThread = null;
}
}
@Override
public void onProviderStateChange(ProviderState state) {
if (state == ProviderState.DISABLED || state == ProviderState.NO_FIX) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
setLocationUnknown();
}
});
}
}
@Override
public void onCurrentLocationChanged(final Location location) {
if (isRecording()) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (location != null) {
setLocation(location);
} else {
setLocationUnknown();
}
}
});
}
}
@Override
public void onCurrentHeadingChanged(double heading) {
// We don't care.
}
@Override
public void onSelectedTrackChanged(Track track, boolean isRecording) {
if (uiUpdateThread == null && isRecording) {
uiUpdateThread = new UiUpdateThread();
uiUpdateThread.start();
} else if (uiUpdateThread != null && !isRecording) {
uiUpdateThread.interrupt();
uiUpdateThread = null;
}
}
@Override
public void onTrackUpdated(final Track track) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (track == null || track.getStatistics() == null) {
statsUtilities.setAllToUnknown();
return;
}
startTime = track.getStatistics().getStartTime();
if (!isRecording()) {
statsUtilities.setTime(R.id.total_time_register, track.getStatistics().getTotalTime());
setLocationUnknown();
}
statsUtilities.setAllStats(track.getStatistics());
}
});
}
@Override
public void clearTrackPoints() {
// We don't care.
}
@Override
public void onNewTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSampledOutTrackPoint(Location loc) {
// We don't care.
}
@Override
public void onSegmentSplit() {
// We don't care.
}
@Override
public void onNewTrackPointsDone() {
// We don't care.
}
@Override
public void clearWaypoints() {
// We don't care.
}
@Override
public void onNewWaypoint(Waypoint wpt) {
// We don't care.
}
@Override
public void onNewWaypointsDone() {
// We don't care.
}
@Override
public boolean onUnitsChanged(boolean metric) {
if (statsUtilities.isMetricUnits() == metric) {
return false;
}
statsUtilities.setMetricUnits(metric);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateLabels();
}
});
return true;
}
@Override
public boolean onReportSpeedChanged(boolean speed) {
if (statsUtilities.isReportSpeed() == speed) {
return false;
}
statsUtilities.setReportSpeed(speed);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
updateLabels();
}
});
return true;
}
/**
* Resumes the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void resumeTrackDataHub() {
trackDataHub = ((MyTracksApplication) getActivity().getApplication()).getTrackDataHub();
trackDataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
}
/**
* Pauses the trackDataHub. Needs to be synchronized because trackDataHub can
* be accessed by multiple threads.
*/
private synchronized void pauseTrackDataHub() {
trackDataHub.unregisterTrackDataListener(this);
trackDataHub = null;
}
/**
* Returns true if recording. Needs to be synchronized because trackDataHub
* can be accessed by multiple threads.
*/
private synchronized boolean isRecording() {
return trackDataHub != null && trackDataHub.isRecordingSelected();
}
/**
* Updates the labels.
*/
private void updateLabels() {
statsUtilities.updateUnits();
statsUtilities.setSpeedLabel(R.id.speed_label, R.string.stat_speed, R.string.stat_pace);
statsUtilities.setSpeedLabels();
}
/**
* Sets the current location.
*
* @param location the current location
*/
private void setLocation(Location location) {
statsUtilities.setAltitude(R.id.elevation_register, location.getAltitude());
statsUtilities.setLatLong(R.id.latitude_register, location.getLatitude());
statsUtilities.setLatLong(R.id.longitude_register, location.getLongitude());
statsUtilities.setSpeed(R.id.speed_register, location.getSpeed() * UnitConversions.MS_TO_KMH);
}
/**
* Sets the current location to unknown.
*/
private void setLocationUnknown() {
statsUtilities.setUnknown(R.id.elevation_register);
statsUtilities.setUnknown(R.id.latitude_register);
statsUtilities.setUnknown(R.id.longitude_register);
statsUtilities.setUnknown(R.id.speed_register);
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.TabHost;
import java.util.HashMap;
/**
* This is a helper class that implements a generic mechanism for associating
* fragments with the tabs in a tab host. It relies on a trick. Normally a tab
* host has a simple API for supplying a View or Intent that each tab will show.
* This is not sufficient for switching between fragments. So instead we make
* the content part of the tab host 0dp high (it is not shown) and the
* TabManager supplies its own dummy view to show as the tab content. It listens
* to changes in tabs, and takes care of switch to the correct fragment shown in
* a separate content area whenever the selected tab changes.
*
* Copied from the Fragment Tabs example in the API 4+ Support Demos.
*
* @author Jimmy Shih
*/
public class TabManager implements TabHost.OnTabChangeListener {
private final FragmentActivity mActivity;
private final TabHost mTabHost;
private final int mContainerId;
private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
TabInfo mLastTab;
static final class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) {
mActivity = activity;
mTabHost = tabHost;
mContainerId = containerId;
mTabHost.setOnTabChangedListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mActivity));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
mTabs.put(tag, info);
mTabHost.addTab(tabSpec);
}
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mActivity, newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
ft.commit();
mActivity.getSupportFragmentManager().executePendingTransactions();
}
}
}
@@ -29,54 +29,63 @@ import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActi
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.AnalyticsUtils;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.RemoteException;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;
import android.widget.CheckBox;
import android.widget.RadioGroup;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.Toast;
import java.util.List;
/**
* An activity to show the track detail.
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
@SuppressWarnings("deprecation")
public class TrackDetailActivity extends TabActivity implements OnTouchListener {
public class TrackDetailActivity extends FragmentActivity {
public static final String TRACK_ID = "track_id";
public static final String WAYPOINT_ID = "waypoint_id";
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_WAYPOINT_ID = "waypoint_id";
private static final String TAG = TrackDetailActivity.class.getSimpleName();
private static final String MARKET_URL_PREFIX = "market://details?id=";
private static final String CURRENT_TAG_KEY = "tab";
private static final int DIALOG_INSTALL_EARTH_ID = 0;
private static final int DIALOG_DELETE_CURRENT_ID = 1;
private static final int DIALOG_CHART_SETTINGS_ID = 2;
private SharedPreferences sharedPreferences;
private TrackDataHub trackDataHub;
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private NavControls navControls;
private TabHost tabHost;
private TabManager tabManager;
private long trackId;
private MenuItem stopRecordingMenuItem;
@@ -88,12 +97,8 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
private MenuItem editMenuItem;
private MenuItem deleteMenuItem;
private final Runnable changeTab = new Runnable() {
public void run() {
getTabHost().setCurrentTab(navControls.getCurrentIcons());
}
};
private View mapViewContainer;
/*
* Note that sharedPreferenceChangeListener cannot be an anonymous inner
* class. Anonymous inner class will get garbage collected.
@@ -109,65 +114,67 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
}
};
/**
* We are not displaying driving directions. Just an arbitrary track that is
* not associated to any licensed mapping data. Therefore it should be okay to
* return false here and still comply with the terms of service.
*/
@Override
protected boolean isRouteDisplayed() {
return false;
}
/**
* We are displaying a location. This needs to return true in order to comply
* with the terms of service.
*/
@Override
protected boolean isLocationDisplayed() {
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
ApiAdapterFactory.getApiAdapter().hideTitle(this);
ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this);
setContentView(R.layout.track_detail);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener);
trackDataHub = ((MyTracksApplication) getApplication()).getTrackDataHub();
trackRecordingServiceConnection = new TrackRecordingServiceConnection(this, null);
final Resources res = getResources();
final TabHost tabHost = getTabHost();
tabHost.addTab(tabHost.newTabSpec(MAP_TAB_TAG)
.setIndicator("Map", res.getDrawable(android.R.drawable.ic_menu_mapmode))
.setContent(new Intent(this, MapActivity.class)));
tabHost.addTab(tabHost.newTabSpec(STATS_TAB_TAG)
.setIndicator("Stats", res.getDrawable(R.drawable.ic_menu_statistics))
.setContent(new Intent(this, StatsActivity.class)));
tabHost.addTab(tabHost.newTabSpec(CHART_TAB_TAG)
.setIndicator("Chart", res.getDrawable(R.drawable.menu_elevation))
.setContent(new Intent(this, ChartActivity.class)));
// Hide the tab widget itself. We'll use overlayed prev/next buttons to
// switch between the tabs:
tabHost.getTabWidget().setVisibility(View.GONE);
RelativeLayout layout = new RelativeLayout(this);
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.setLayoutParams(params);
navControls = new NavControls(this, layout, getResources().obtainTypedArray(R.array.left_icons),
getResources().obtainTypedArray(R.array.right_icons), changeTab);
navControls.show();
tabHost.addView(layout);
layout.setOnTouchListener(this);
// Get the trackid
Intent intent = getIntent();
trackId = intent.getLongExtra(TRACK_ID, -1L);
if (trackId == -1L) {
startTrackListActivity();
finish();
}
trackDataHub.loadTrack(trackId);
// Get the waypointId
long waypointId = intent.getLongExtra(WAYPOINT_ID, -1L);
if (waypointId != -1L) {
MapActivity mapActivity = getMapActivity();
if (mapActivity != null) {
getTabHost().setCurrentTab(0);
mapActivity.showWaypoint(trackId, waypointId);
} else {
Log.e(TAG, "MapActivity is null");
}
mapViewContainer = getLayoutInflater().inflate(R.layout.mytracks_layout, null);
tabHost = (TabHost) findViewById(android.R.id.tabhost);
tabHost.setup();
tabManager = new TabManager(this, tabHost, R.id.realtabcontent);
TabSpec mapTabSpec = tabHost.newTabSpec(MAP_TAB_TAG).setIndicator(
getString(R.string.track_detail_map_tab),
getResources().getDrawable(android.R.drawable.ic_menu_mapmode));
tabManager.addTab(mapTabSpec, MapFragment.class, null);
TabSpec chartTabSpec = tabHost.newTabSpec(CHART_TAB_TAG).setIndicator(
getString(R.string.track_detail_chart_tab),
getResources().getDrawable(R.drawable.menu_elevation));
tabManager.addTab(chartTabSpec, ChartFragment.class, null);
TabSpec statsTabSpec = tabHost.newTabSpec(STATS_TAB_TAG).setIndicator(
getString(R.string.track_detail_stats_tab),
getResources().getDrawable(R.drawable.ic_menu_statistics));
tabManager.addTab(statsTabSpec, StatsFragment.class, null);
if (savedInstanceState != null) {
tabHost.setCurrentTabByTag(savedInstanceState.getString(CURRENT_TAG_KEY));
}
handleIntent(getIntent());
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
@Override
protected void onStart() {
super.onStart();
@@ -180,6 +187,12 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
trackRecordingServiceConnection.bindIfRunning();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(CURRENT_TAG_KEY, tabHost.getCurrentTabTag());
}
@Override
protected void onStop() {
super.onStop();
@@ -196,7 +209,18 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_INSTALL_EARTH_ID:
return PlayTrackUtils.createInstallEarthDialog(this);
return new AlertDialog.Builder(this).setCancelable(true)
.setMessage(R.string.track_detail_install_earth_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent()
.setData(Uri.parse(MARKET_URL_PREFIX + SaveActivity.GOOGLE_EARTH_PACKAGE));
startActivity(intent);
}
})
.create();
case DIALOG_DELETE_CURRENT_ID:
return DialogUtils.createConfirmationDialog(this,
R.string.track_detail_delete_confirm_message, new DialogInterface.OnClickListener() {
@@ -206,33 +230,85 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
startTrackListActivity();
}
});
case DIALOG_CHART_SETTINGS_ID:
return createChartSettingsDialog();
default:
return null;
}
}
private Dialog createChartSettingsDialog() {
final ChartFragment chartFragment = (ChartFragment) getSupportFragmentManager()
.findFragmentByTag(CHART_TAB_TAG);
View view = getLayoutInflater().inflate(R.layout.chart_settings, null);
final RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.chart_settings_x);
radioGroup
.check(chartFragment.getMode() == ChartView.Mode.BY_DISTANCE ? R.id.chart_settings_by_distance
: R.id.chart_settings_by_time);
final CheckBox[] checkBoxes = new CheckBox[ChartView.NUM_SERIES];
checkBoxes[ChartView.ELEVATION_SERIES] = (CheckBox) view
.findViewById(R.id.chart_settings_elevation);
checkBoxes[ChartView.SPEED_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_speed);
checkBoxes[ChartView.POWER_SERIES] = (CheckBox) view.findViewById(R.id.chart_settings_power);
checkBoxes[ChartView.CADENCE_SERIES] = (CheckBox) view
.findViewById(R.id.chart_settings_cadence);
checkBoxes[ChartView.HEART_RATE_SERIES] = (CheckBox) view
.findViewById(R.id.chart_settings_heart_rate);
// set checkboxes values
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
checkBoxes[i].setChecked(chartFragment.isChartValueSeriesEnabled(i));
}
checkBoxes[ChartView.SPEED_SERIES].setText(chartFragment.isReportSpeed() ? R.string.stat_speed
: R.string.stat_pace);
return new AlertDialog.Builder(this).setCancelable(true)
.setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
chartFragment
.setMode(radioGroup.getCheckedRadioButtonId() == R.id.chart_settings_by_distance ? ChartView.Mode.BY_DISTANCE
: ChartView.Mode.BY_TIME);
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chartFragment.setChartValueSeriesEnabled(i, checkBoxes[i].isChecked());
}
chartFragment.update();
}
}).setTitle(R.string.menu_chart_settings).setView(view).create();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_detail, menu);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.findItem(R.id.menu_save_gpx).setTitle(getString(R.string.menu_save_format, fileTypes[0]));
menu.findItem(R.id.menu_save_kml).setTitle(getString(R.string.menu_save_format, fileTypes[1]));
menu.findItem(R.id.menu_save_csv).setTitle(getString(R.string.menu_save_format, fileTypes[2]));
menu.findItem(R.id.menu_save_tcx).setTitle(getString(R.string.menu_save_format, fileTypes[3]));
menu.findItem(R.id.track_detail_save_gpx)
.setTitle(getString(R.string.menu_save_format, fileTypes[0]));
menu.findItem(R.id.track_detail_save_kml)
.setTitle(getString(R.string.menu_save_format, fileTypes[1]));
menu.findItem(R.id.track_detail_save_csv)
.setTitle(getString(R.string.menu_save_format, fileTypes[2]));
menu.findItem(R.id.track_detail_save_tcx)
.setTitle(getString(R.string.menu_save_format, fileTypes[3]));
menu.findItem(R.id.menu_share_gpx).setTitle(getString(R.string.menu_share_file, fileTypes[0]));
menu.findItem(R.id.menu_share_kml).setTitle(getString(R.string.menu_share_file, fileTypes[1]));
menu.findItem(R.id.menu_share_csv).setTitle(getString(R.string.menu_share_file, fileTypes[2]));
menu.findItem(R.id.menu_share_tcx).setTitle(getString(R.string.menu_share_file, fileTypes[3]));
menu.findItem(R.id.track_detail_share_gpx)
.setTitle(getString(R.string.menu_share_file, fileTypes[0]));
menu.findItem(R.id.track_detail_share_kml)
.setTitle(getString(R.string.menu_share_file, fileTypes[1]));
menu.findItem(R.id.track_detail_share_csv)
.setTitle(getString(R.string.menu_share_file, fileTypes[2]));
menu.findItem(R.id.track_detail_share_tcx)
.setTitle(getString(R.string.menu_share_file, fileTypes[3]));
stopRecordingMenuItem = menu.findItem(R.id.menu_stop_recording);
insertMarkerMenuItem = menu.findItem(R.id.menu_insert_marker);
playMenuItem = menu.findItem(R.id.menu_play);
shareMenuItem = menu.findItem(R.id.menu_share);
sendGoogleMenuItem = menu.findItem(R.id.menu_send_google);
saveMenuItem = menu.findItem(R.id.menu_save);
editMenuItem = menu.findItem(R.id.menu_edit);
deleteMenuItem = menu.findItem(R.id.menu_delete);
stopRecordingMenuItem = menu.findItem(R.id.track_detail_stop_recording);
insertMarkerMenuItem = menu.findItem(R.id.track_detail_insert_marker);
playMenuItem = menu.findItem(R.id.track_detail_play);
shareMenuItem = menu.findItem(R.id.track_detail_share);
sendGoogleMenuItem = menu.findItem(R.id.track_detail_send_google);
saveMenuItem = menu.findItem(R.id.track_detail_save);
editMenuItem = menu.findItem(R.id.track_detail_edit);
deleteMenuItem = menu.findItem(R.id.track_detail_delete);
updateMenu();
return true;
@@ -240,14 +316,15 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
String currentTabTag = getTabHost().getCurrentTabTag();
menu.findItem(R.id.menu_chart_settings).setVisible(CHART_TAB_TAG.equals(currentTabTag));
menu.findItem(R.id.menu_my_location).setVisible(MAP_TAB_TAG.equals(currentTabTag));
String currentTabTag = tabHost.getCurrentTabTag();
menu.findItem(R.id.track_detail_chart_settings).setVisible(CHART_TAB_TAG.equals(currentTabTag));
menu.findItem(R.id.track_detail_my_location).setVisible(MAP_TAB_TAG.equals(currentTabTag));
// Set map or satellite mode
MapActivity mapActivity = getMapActivity();
boolean isSatelliteMode = mapActivity != null ? mapActivity.isSatelliteView() : false;
menu.findItem(R.id.menu_satellite_mode).setVisible(MAP_TAB_TAG.equals(currentTabTag))
MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
.findFragmentByTag(MAP_TAB_TAG);
boolean isSatelliteMode = mapFragment != null ? mapFragment.isSatelliteView() : false;
menu.findItem(R.id.track_detail_satellite_mode).setVisible(MAP_TAB_TAG.equals(currentTabTag))
.setTitle(isSatelliteMode ? R.string.menu_map_mode : R.string.menu_satellite_mode);
return super.onPrepareOptionsMenu(menu);
@@ -255,103 +332,105 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
@Override
public boolean onOptionsItemSelected(MenuItem item) {
MapActivity mapActivity;
MapFragment mapFragment;
Intent intent;
switch (item.getItemId()) {
case android.R.id.home:
startTrackListActivity();
return true;
case R.id.menu_stop_recording:
case R.id.track_detail_stop_recording:
updateMenuItems(false);
stopRecording();
return true;
case R.id.menu_insert_marker:
case R.id.track_detail_insert_marker:
// TODO: Add insert marker when updating WaypointList to ICS
return true;
case R.id.menu_play:
if (PlayTrackUtils.isEarthInstalled(this)) {
PlayTrackUtils.playTrack(this, trackId);
case R.id.track_detail_play:
if (isEarthInstalled()) {
AnalyticsUtils.sendPageViews(this, "/action/play");
intent = new Intent(this, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.KML)
.putExtra(SaveActivity.EXTRA_PLAY_TRACK, true);
startActivity(intent);
} else {
showDialog(DIALOG_INSTALL_EARTH_ID);
}
return true;
case R.id.menu_share_map:
case R.id.track_detail_share_map:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, false, false));
startActivity(intent);
return true;
case R.id.menu_share_fusion_table:
case R.id.track_detail_share_fusion_table:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, false, true, false));
startActivity(intent);
return true;
case R.id.menu_share_gpx:
case R.id.track_detail_share_gpx:
startSaveActivity(TrackFileFormat.GPX, true);
return true;
case R.id.menu_share_kml:
case R.id.track_detail_share_kml:
startSaveActivity(TrackFileFormat.KML, true);
return true;
case R.id.menu_share_csv:
case R.id.track_detail_share_csv:
startSaveActivity(TrackFileFormat.CSV, true);
return true;
case R.id.menu_share_tcx:
case R.id.track_detail_share_tcx:
startSaveActivity(TrackFileFormat.TCX, true);
return true;
case R.id.menu_markers:
case R.id.track_detail_markers:
intent = new Intent(this, WaypointsList.class)
.putExtra("trackid", trackDataHub.getSelectedTrackId());
startActivityForResult(intent, Constants.SHOW_WAYPOINT);
return true;
case R.id.menu_send_google:
case R.id.track_detail_send_google:
intent = new Intent(this, UploadServiceChooserActivity.class)
.putExtra(SendRequest.SEND_REQUEST_KEY, new SendRequest(trackId, true, true, true));
startActivity(intent);
return true;
case R.id.menu_save_gpx:
case R.id.track_detail_save_gpx:
startSaveActivity(TrackFileFormat.GPX, false);
return true;
case R.id.menu_save_kml:
case R.id.track_detail_save_kml:
startSaveActivity(TrackFileFormat.KML, false);
return true;
case R.id.menu_save_csv:
case R.id.track_detail_save_csv:
startSaveActivity(TrackFileFormat.CSV, false);
return true;
case R.id.menu_save_tcx:
case R.id.track_detail_save_tcx:
startSaveActivity(TrackFileFormat.TCX, false);
return true;
case R.id.menu_edit:
case R.id.track_detail_edit:
startActivity(new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.TRACK_ID, trackId));
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId));
return true;
case R.id.menu_delete:
case R.id.track_detail_delete:
showDialog(DIALOG_DELETE_CURRENT_ID);
return true;
case R.id.menu_my_location:
mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.showMyLocation();
case R.id.track_detail_my_location:
mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
if (mapFragment != null) {
mapFragment.showMyLocation();
}
return true;
case R.id.menu_satellite_mode:
mapActivity = getMapActivity();
if (mapActivity != null) {
mapActivity.setSatelliteView(!mapActivity.isSatelliteView());
case R.id.track_detail_satellite_mode:
mapFragment = (MapFragment) getSupportFragmentManager().findFragmentByTag(MAP_TAB_TAG);
if (mapFragment != null) {
mapFragment.setSatelliteView(!mapFragment.isSatelliteView());
}
return true;
case R.id.menu_chart_settings:
ChartActivity chartActivity = getChartActivity();
if (chartActivity != null) {
chartActivity.showChartSettingsDialog();
}
case R.id.track_detail_chart_settings:
showDialog(DIALOG_CHART_SETTINGS_ID);
return true;
case R.id.menu_sensor_state:
case R.id.track_detail_sensor_state:
startActivity(new Intent(this, SensorStateActivity.class));
return true;
case R.id.menu_settings:
case R.id.track_detail_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, WelcomeActivity.class));
case R.id.track_detail_help:
startActivity(new Intent(this, HelpActivity.class));
return true;
default:
return false;
@@ -367,10 +446,11 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
if (results != null) {
long waypointId = results.getLongExtra(WaypointDetails.WAYPOINT_ID_EXTRA, -1L);
if (waypointId != -1L) {
MapActivity mapActivity = (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
if (mapActivity != null) {
getTabHost().setCurrentTab(0);
mapActivity.showWaypoint(waypointId);
MapFragment mapFragment = (MapFragment) getSupportFragmentManager()
.findFragmentByTag(MAP_TAB_TAG);
if (mapFragment != null) {
tabHost.setCurrentTab(0);
mapFragment.showWaypoint(waypointId);
}
}
}
@@ -406,12 +486,37 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
return super.onTrackballEvent(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
navControls.show();
/**
* @return the mapViewContainer
*/
public View getMapViewContainer() {
return mapViewContainer;
}
/**
* Handles the data in the intent.
*/
private void handleIntent(Intent intent) {
// Get the trackid
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId == -1L) {
startTrackListActivity();
finish();
}
trackDataHub.loadTrack(trackId);
// Get the waypointId
long waypointId = intent.getLongExtra(EXTRA_WAYPOINT_ID, -1L);
if (waypointId != -1L) {
MapFragment mapFragmet = (MapFragment) getSupportFragmentManager()
.findFragmentByTag(MAP_TAB_TAG);
if (mapFragmet != null) {
tabHost.setCurrentTab(0);
mapFragmet.showWaypoint(trackId, waypointId);
} else {
Log.e(TAG, "MapFragment is null");
}
}
return false;
}
/**
@@ -458,7 +563,6 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
* Deletes the current track.
*/
private void deleteCurrentTrack() {
long trackId = trackDataHub.getSelectedTrackId();
MyTracksProviderUtils.Factory.get(TrackDetailActivity.this).deleteTrack(trackId);
}
@@ -511,23 +615,24 @@ public class TrackDetailActivity extends TabActivity implements OnTouchListener
long recordingTrackId = sharedPreferences.getLong(getString(R.string.recording_track_key), -1L);
if (recordingTrackId != -1L) {
Intent intent = new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.SHOW_CANCEL, false)
.putExtra(TrackEditActivity.TRACK_ID, recordingTrackId);
.putExtra(TrackEditActivity.EXTRA_SHOW_CANCEL, false)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, recordingTrackId);
startActivity(intent);
}
}
/**
* Gets the map activity, can be null.
* Returns true if Google Earth app is installed.
*/
private MapActivity getMapActivity() {
return (MapActivity) getLocalActivityManager().getActivity(MAP_TAB_TAG);
}
/**
* Gets the chart activity, can be null.
*/
private ChartActivity getChartActivity() {
return (ChartActivity) getLocalActivityManager().getActivity(CHART_TAB_TAG);
private boolean isEarthInstalled() {
List<ResolveInfo> infos = getPackageManager().queryIntentActivities(
new Intent().setType(SaveActivity.KML_MIME_TYPE), PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : infos) {
if (info.activityInfo != null && info.activityInfo.packageName != null
&& info.activityInfo.packageName.equals(SaveActivity.GOOGLE_EARTH_PACKAGE)) {
return true;
}
}
return false;
}
}
@@ -13,15 +13,19 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
@@ -32,13 +36,13 @@ import android.widget.EditText;
/**
* An activity that let's the user see and edit the user editable track meta
* data such as track name, activity type, and track description.
*
*
* @author Leif Hendrik Wilden
*/
public class TrackEditActivity extends Activity implements OnClickListener {
public static final String TRACK_ID = "trackId";
public static final String SHOW_CANCEL = "showCancel";
public static final String EXTRA_TRACK_ID = "track_id";
public static final String EXTRA_SHOW_CANCEL = "show_cancel";
private static final String TAG = TrackEditActivity.class.getSimpleName();
@@ -46,33 +50,34 @@ public class TrackEditActivity extends Activity implements OnClickListener {
private MyTracksProviderUtils myTracksProviderUtils;
private Track track;
private EditText trackName;
private EditText name;
private AutoCompleteTextView activityType;
private EditText trackDescription;
private EditText description;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this);
setContentView(R.layout.track_edit);
trackId = getIntent().getLongExtra(TRACK_ID, -1);
if (trackId < 0) {
Log.e(TAG, "invalid trackId.");
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L);
if (trackId == -1L) {
Log.e(TAG, "invalid trackId");
finish();
return;
}
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(this);
track = myTracksProviderUtils.getTrack(trackId);
if (track == null) {
Log.e(TAG, "no track.");
Log.e(TAG, "no track");
finish();
return;
}
trackName = (EditText) findViewById(R.id.track_edit_track_name);
trackName.setText(track.getName());
name = (EditText) findViewById(R.id.track_edit_name);
name.setText(track.getName());
activityType = (AutoCompleteTextView) findViewById(R.id.track_edit_activity_type);
activityType.setText(track.getCategory());
@@ -80,40 +85,40 @@ public class TrackEditActivity extends Activity implements OnClickListener {
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
this, R.array.activity_types, android.R.layout.simple_dropdown_item_1line);
activityType.setAdapter(adapter);
trackDescription = (EditText) findViewById(R.id.track_edit_track_description);
trackDescription.setText(track.getDescription());
description = (EditText) findViewById(R.id.track_edit_description);
description.setText(track.getDescription());
Button save = (Button) findViewById(R.id.track_edit_save);
save.setOnClickListener(this);
Button cancel = (Button) findViewById(R.id.track_edit_cancel);
if (getIntent().getBooleanExtra(SHOW_CANCEL, true)) {
if (getIntent().getBooleanExtra(EXTRA_SHOW_CANCEL, true)) {
setTitle(R.string.menu_edit);
cancel.setOnClickListener(this);
cancel.setVisibility(View.VISIBLE);
} else {
cancel.setVisibility(View.INVISIBLE);
setTitle(R.string.track_edit_new_track_title);
cancel.setVisibility(View.GONE);
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.track_edit_save:
save();
finish();
break;
case R.id.track_edit_cancel:
finish();
break;
default:
finish();
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() != android.R.id.home) {
return false;
}
finish();
return true;
}
private void save() {
track.setName(trackName.getText().toString());
track.setCategory(activityType.getText().toString());
track.setDescription(trackDescription.getText().toString());
myTracksProviderUtils.updateTrack(track);
@Override
public void onClick(View view) {
if (view.getId() == R.id.track_edit_save) {
track.setName(name.getText().toString());
track.setCategory(activityType.getText().toString());
track.setDescription(description.getText().toString());
myTracksProviderUtils.updateTrack(track);
}
finish();
}
}
@@ -16,23 +16,19 @@
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.fragments.DeleteAllDialogFragment;
import com.google.android.apps.mytracks.fragments.EulaDialogFragment;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CheckUnitsUtils;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.EulaUtils;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -40,10 +36,8 @@ import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.speech.tts.TextToSpeech;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -67,12 +61,6 @@ import android.widget.Toast;
public class TrackListActivity extends FragmentActivity {
private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String CHECK_UNITS_DIALOG_TAG = "checkUnitsDialog";
private static final String DELETE_ALL_DIALOG_TAG = "deleteAllDialog";
private static final String EULA_DIALOG_TAG = "eulaDialog";
private static final String EXPORT_ALL_DIALOG_TAG = "exportAllDialog";
private static final int WELCOME_ACTIVITY_REQUEST_CODE = 0;
private static final String[] PROJECTION = new String[] {
TracksColumns._ID,
@@ -171,7 +159,7 @@ public class TrackListActivity extends FragmentActivity {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(TrackListActivity.this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(TrackDetailActivity.TRACK_ID, id);
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, id);
startActivity(intent);
}
});
@@ -240,9 +228,11 @@ public class TrackListActivity extends FragmentActivity {
});
if (!EulaUtils.getEulaValue(this)) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag(EULA_DIALOG_TAG);
Fragment fragment = getSupportFragmentManager()
.findFragmentByTag(EulaDialogFragment.EULA_DIALOG_TAG);
if (fragment == null) {
new EulaDialogFragment().show(getSupportFragmentManager(), EULA_DIALOG_TAG);
EulaDialogFragment.newInstance(false).show(
getSupportFragmentManager(), EulaDialogFragment.EULA_DIALOG_TAG);
}
}
}
@@ -259,34 +249,25 @@ public class TrackListActivity extends FragmentActivity {
trackRecordingServiceConnection.unbind();
}
@Override
public void onActivityResult(int requestCode, int resultCode, final Intent results) {
if (requestCode == WELCOME_ACTIVITY_REQUEST_CODE) {
if (!CheckUnitsUtils.getCheckUnitsValue(this)) {
/*
* See bug http://code.google.com/p/android/issues/detail?id=23761.
* Cannot use
* CheckUnitsDialogFragment().show(getSupportFragmentManager(),
* CHECK_UNITS_DIALOG_TAG). Need to use commitAllowingStateLoss with the
* support package.
*/
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(new CheckUnitsDialogFragment(), CHECK_UNITS_DIALOG_TAG);
fragmentTransaction.commitAllowingStateLoss();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.track_list, menu);
recordTrack = menu.findItem(R.id.menu_record_track);
stopRecording = menu.findItem(R.id.menu_stop_recording);
search = menu.findItem(R.id.menu_search);
importAll = menu.findItem(R.id.menu_import_all);
exportAll = menu.findItem(R.id.menu_export_all);
deleteAll = menu.findItem(R.id.menu_delete_all);
String fileTypes[] = getResources().getStringArray(R.array.file_types);
menu.findItem(R.id.track_list_export_gpx)
.setTitle(getString(R.string.menu_export_all_format, fileTypes[0]));
menu.findItem(R.id.track_list_export_kml)
.setTitle(getString(R.string.menu_export_all_format, fileTypes[1]));
menu.findItem(R.id.track_list_export_csv)
.setTitle(getString(R.string.menu_export_all_format, fileTypes[2]));
menu.findItem(R.id.track_list_export_tcx)
.setTitle(getString(R.string.menu_export_all_format, fileTypes[3]));
recordTrack = menu.findItem(R.id.track_list_record_track);
stopRecording = menu.findItem(R.id.track_list_stop_recording);
search = menu.findItem(R.id.track_list_search);
importAll = menu.findItem(R.id.track_list_import_all);
exportAll = menu.findItem(R.id.track_list_export_all);
deleteAll = menu.findItem(R.id.track_list_delete_all);
ApiAdapterFactory.getApiAdapter().configureSearchWidget(this, search);
updateMenu();
@@ -327,34 +308,48 @@ public class TrackListActivity extends FragmentActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_record_track:
case R.id.track_list_record_track:
updateMenuItems(true);
startRecording();
return true;
case R.id.menu_stop_recording:
case R.id.track_list_stop_recording:
updateMenuItems(false);
stopRecording();
return true;
case R.id.menu_search:
case R.id.track_list_search:
return ApiAdapterFactory.getApiAdapter().handleSearchMenuSelection(this);
case R.id.menu_import_all:
case R.id.track_list_import_all:
startActivity(
new Intent(this, ImportActivity.class).putExtra(ImportActivity.EXTRA_IMPORT_ALL, true));
return true;
case R.id.menu_export_all:
new ExportAllDialogFragment().show(getSupportFragmentManager(), EXPORT_ALL_DIALOG_TAG);
case R.id.track_list_export_gpx:
startActivity(new Intent(this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.GPX));
return true;
case R.id.menu_delete_all:
new DeleteAllDialogFragment().show(getSupportFragmentManager(), DELETE_ALL_DIALOG_TAG);
case R.id.track_list_export_kml:
startActivity(new Intent(this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.KML));
return true;
case R.id.menu_aggregated_statistics:
case R.id.track_list_export_csv:
startActivity(new Intent(this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.CSV));
return true;
case R.id.track_list_export_tcx:
startActivity(new Intent(this, ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.TCX));
return true;
case R.id.track_list_delete_all:
new DeleteAllDialogFragment().show(
getSupportFragmentManager(), DeleteAllDialogFragment.DELETE_ALL_DIALOG_TAG);
return true;
case R.id.track_list_aggregated_statistics:
startActivity(new Intent(this, AggregatedStatsActivity.class));
return true;
case R.id.menu_settings:
case R.id.track_list_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_help:
startActivity(new Intent(this, WelcomeActivity.class));
case R.id.track_list_help:
startActivity(new Intent(this, HelpActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
@@ -392,14 +387,20 @@ public class TrackListActivity extends FragmentActivity {
ITrackRecordingService service = trackRecordingServiceConnection.getServiceIfBound();
if (service != null) {
try {
/*
* Remembers the recordingTrackId before endCurrentTrack sets the shared
* preferences, R.string.recording_track_key, to -1L, and the
* sharedPreferenceChangedListener sets the recordingTrackId variable to
* -1L.
*/
long trackId = recordingTrackId;
service.endCurrentTrack();
if (recordingTrackId != -1L) {
if (trackId != -1L) {
Intent intent = new Intent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.SHOW_CANCEL, false)
.putExtra(TrackEditActivity.TRACK_ID, recordingTrackId);
.putExtra(TrackEditActivity.EXTRA_SHOW_CANCEL, false)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
startActivity(intent);
}
recordingTrackId = -1L;
} catch (Exception e) {
Log.d(TAG, "Unable to stop recording.", e);
}
@@ -416,117 +417,4 @@ public class TrackListActivity extends FragmentActivity {
}
return super.onKeyUp(keyCode, event);
}
/**
* Check Units DialogFragment.
*/
public static class CheckUnitsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity()).setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
int position = ((AlertDialog) dialog).getListView().getSelectedItemPosition();
SharedPreferences sharedPreferences = getActivity()
.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(sharedPreferences.edit()
.putBoolean(getString(R.string.metric_units_key), position == 0));
}
})
.setSingleChoiceItems(new CharSequence[] { getString(R.string.preferred_units_metric),
getString(R.string.preferred_units_imperial) }, 0, null)
.setTitle(R.string.preferred_units_title)
.create();
}
}
/**
* Delete All DialogFragment.
*/
public static class DeleteAllDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return DialogUtils.createConfirmationDialog(getActivity(),
R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(getActivity()).deleteAllTracks();
}
});
}
};
/**
* Eula DialogFragment.
*/
public static class EulaDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setMessage(EulaUtils.getEulaMessage(getActivity()))
.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
getActivity().finish();
}
})
.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaUtils.setEulaValue(getActivity());
getActivity().startActivityForResult(
new Intent(getActivity(), WelcomeActivity.class), WELCOME_ACTIVITY_REQUEST_CODE);
}
})
.setTitle(R.string.eula_title)
.create();
}
}
/**
* Export All DialogFragment.
*/
public static class ExportAllDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
String fileTypes[] = getResources().getStringArray(R.array.file_types);
String[] choices = new String[fileTypes.length];
for (int i = 0; i < fileTypes.length; i++) {
choices[i] = getString(R.string.menu_export_all_format, fileTypes[i]);
}
return new AlertDialog.Builder(getActivity()).setNegativeButton(R.string.generic_cancel, null)
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
Intent intent = new Intent(getActivity(), ExportActivity.class).putExtra(
ExportActivity.EXTRA_TRACK_FILE_FORMAT,
(Parcelable) TrackFileFormat.values()[index]);
getActivity().startActivity(intent);
}
})
.setSingleChoiceItems(choices, 0, null)
.setTitle(R.string.menu_export_all)
.create();
}
}
}
@@ -1,87 +0,0 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.util.EulaUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
/**
* An activity that displays a welcome screen.
*
* @author Sandor Dornbush
*/
public class WelcomeActivity extends Activity {
private static final int DIALOG_ABOUT_ID = 0;
private static final int DIALOG_EULA_ID = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.welcome);
findViewById(R.id.welcome_ok).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
finish();
}
});
findViewById(R.id.welcome_about).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showDialog(DIALOG_ABOUT_ID);
}
});
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_ABOUT_ID:
View view = getLayoutInflater().inflate(R.layout.about, null);
TextView aboutVersion = (TextView) view.findViewById(R.id.about_version);
aboutVersion.setText(SystemUtils.getMyTracksVersion(this));
return new AlertDialog.Builder(this)
.setCancelable(true)
.setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showDialog(DIALOG_EULA_ID);
}
})
.setPositiveButton(R.string.generic_ok, null)
.setTitle(R.string.my_tracks_app_name)
.setView(view)
.create();
case DIALOG_EULA_ID:
return new AlertDialog.Builder(this)
.setCancelable(true)
.setMessage(EulaUtils.getEulaMessage(this))
.setPositiveButton(R.string.generic_ok, null)
.setTitle(R.string.eula_title)
.create();
default:
return null;
}
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.util.SystemUtils;
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.support.v4.app.DialogFragment;
import android.view.View;
import android.widget.TextView;
/**
* A DialogFragment to show information about My Tracks.
*
* @author Jimmy Shih
*/
public class AboutDialogFragment extends DialogFragment {
public static final String ABOUT_DIALOG_TAG = "aboutDialog";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.about, null);
TextView aboutVersion = (TextView) view.findViewById(R.id.about_version);
aboutVersion.setText(SystemUtils.getMyTracksVersion(getActivity()));
return new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setNegativeButton(R.string.about_license, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaDialogFragment.newInstance(true).show(
getActivity().getSupportFragmentManager(), EulaDialogFragment.EULA_DIALOG_TAG);
}
})
.setPositiveButton(R.string.generic_ok, null)
.setTitle(R.string.help_about)
.setView(view)
.create();
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.util.ApiAdapterFactory;
import com.google.android.apps.mytracks.util.CheckUnitsUtils;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
/**
* A DialogFragment to check preferred units.
*
* @author Jimmy Shih
*/
public class CheckUnitsDialogFragment extends DialogFragment {
public static final String CHECK_UNITS_DIALOG_TAG = "checkUnitsDialog";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
CheckUnitsUtils.setCheckUnitsValue(getActivity());
int position = ((AlertDialog) dialog).getListView().getSelectedItemPosition();
SharedPreferences sharedPreferences = getActivity()
.getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE);
ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(sharedPreferences.edit()
.putBoolean(getString(R.string.metric_units_key), position == 0));
}
})
.setSingleChoiceItems(new CharSequence[] { getString(R.string.preferred_units_metric),
getString(R.string.preferred_units_imperial) }, 0, null)
.setTitle(R.string.preferred_units_title)
.create();
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
/**
* A DialogFragment to delete all tracks.
*
* @author Jimmy Shih
*/
public class DeleteAllDialogFragment extends DialogFragment {
public static final String DELETE_ALL_DIALOG_TAG = "deleteAllDialog";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return DialogUtils.createConfirmationDialog(getActivity(),
R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyTracksProviderUtils.Factory.get(getActivity()).deleteAllTracks();
}
});
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
import com.google.android.apps.mytracks.util.EulaUtils;
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.support.v4.app.DialogFragment;
/**
* A DialogFragment to show EULA.
*
* @author Jimmy Shih
*/
public class EulaDialogFragment extends DialogFragment {
public static final String EULA_DIALOG_TAG = "eulaDialog";
private static final String KEY_HAS_ACCEPTED = "hasAccepted";
/**
* Creates a new instance of {@link EulaDialogFragment}.
*
* @param hasAccepted true if the user has accepted the eula.
*/
public static EulaDialogFragment newInstance(boolean hasAccepted) {
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_HAS_ACCEPTED, hasAccepted);
EulaDialogFragment eulaDialogFragment = new EulaDialogFragment();
eulaDialogFragment.setArguments(bundle);
return eulaDialogFragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
boolean hasAccepted = getArguments().getBoolean(KEY_HAS_ACCEPTED);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setMessage(EulaUtils.getEulaMessage(getActivity()))
.setTitle(R.string.eula_title);
if (hasAccepted) {
builder.setPositiveButton(R.string.generic_ok, null);
} else {
builder.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
getActivity().finish();
}
})
.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EulaUtils.setEulaValue(getActivity());
new WelcomeDialogFragment().show(
getActivity().getSupportFragmentManager(), WelcomeDialogFragment.WELCOME_DIALOG_TAG);
}
});
}
return builder.create();
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.fragments;
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.support.v4.app.DialogFragment;
import android.view.View;
/**
* A DialogFrament to show the welcome info.
*
* @author Jimmy Shih
*/
public class WelcomeDialogFragment extends DialogFragment {
public static final String WELCOME_DIALOG_TAG = "welcomeDialog";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.welcome, null);
return new AlertDialog.Builder(getActivity())
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
checkUnits();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
checkUnits();
}
})
.setTitle(R.string.welcome_title)
.setView(view)
.create();
}
private void checkUnits() {
new CheckUnitsDialogFragment().show(
getActivity().getSupportFragmentManager(), CheckUnitsDialogFragment.CHECK_UNITS_DIALOG_TAG);
}
}
@@ -20,7 +20,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PlayTrackUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -48,8 +47,13 @@ public class SaveActivity extends Activity {
public static final String EXTRA_SHARE_TRACK = "share_track";
public static final String EXTRA_PLAY_TRACK = "play_track";
public static final String KML_MIME_TYPE = "application/vnd.google-earth.kml+xml";
public static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
private static final String TAG = SaveActivity.class.getSimpleName();
private static final String TOUR_FEATURE_ID = "com.google.earth.EXTRA.tour_feature_id";
private static final String GOOGLE_EARTH_CLASS = "com.google.earth.EarthActivity";
private static final int DIALOG_PROGRESS_ID = 0;
private static final int DIALOG_RESULT_ID = 1;
@@ -207,9 +211,9 @@ public class SaveActivity extends Activity {
} else if (playTrack) {
Intent intent = new Intent()
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
.setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS)
.setDataAndType(Uri.fromFile(new File(filePath)), PlayTrackUtils.KML_MIME_TYPE);
.putExtra(TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
.setClassName(GOOGLE_EARTH_PACKAGE, GOOGLE_EARTH_CLASS)
.setDataAndType(Uri.fromFile(new File(filePath)), KML_MIME_TYPE);
startActivity(intent);
}
}
@@ -486,7 +486,7 @@ public class TrackRecordingService extends Service {
System.currentTimeMillis());
Intent intent = new Intent(this, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(TrackDetailActivity.TRACK_ID, recordingTrackId);
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, recordingTrackId);
PendingIntent contentIntent = PendingIntent.getActivity(
this, 0 /* requestCode */, intent, 0 /* flags */);
notification.setLatestEventInfo(this, getString(R.string.my_tracks_app_name),
@@ -32,13 +32,18 @@ import android.widget.SearchView;
@TargetApi(11)
public class Api11Adapter extends Api10Adapter {
@Override
public void hideTitle(Activity activity) {
// Do nothing
}
@Override
public void configureActionBarHomeAsUp(Activity activity) {
ActionBar actionBar = activity.getActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE);
@@ -104,17 +104,20 @@ public class Api7Adapter implements ApiAdapter {
}
@Override
public void configureActionBarHomeAsUp(Activity activity) {
// Action bar not available, just hide the title and let actions be shown
// via the regular menu.
public void hideTitle(Activity activity) {
activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
@Override
public void configureActionBarHomeAsUp(Activity activity) {
// Do nothing
}
@Override
public void configureSearchWidget(Activity activity, MenuItem menuItem) {
// Do nothing
// Do nothing
}
@Override
public boolean handleSearchMenuSelection(Activity activity) {
activity.onSearchRequested();
@@ -100,14 +100,26 @@ public interface ApiAdapter {
* @param bluetoothDevice
*/
public BluetoothSocket getBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException;
/**
* Hides the title. If the platform supports the action bar, do nothing.
* Ideally, with the action bar, we would like to collapse the navigation tabs
* into the action bar. However, collapsing is not supported by the
* compatibility library.
* <p>
* Due to changes in API level 11.
*
* @param activity the activity
*/
public void hideTitle(Activity activity);
/**
* Configures the action bar with the Home button as an Up button. If the
* system doesn't support action bar, hide the title bar.
* platform doesn't support the action bar, do nothing.
* <p>
* Due to changes in API level 11.
*
* @param activity the current activity
* @param activity the activity
*/
public void configureActionBarHomeAsUp(Activity activity);
@@ -1,108 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.maps.mytracks.R;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Parcelable;
import java.util.List;
/**
* Utilities for playing a track on Google Earth.
*
* @author Jimmy Shih
*/
public class PlayTrackUtils {
/**
* KML mime type.
*/
public static final String KML_MIME_TYPE = "application/vnd.google-earth.kml+xml";
public static final String TOUR_FEATURE_ID = "com.google.earth.EXTRA.tour_feature_id";
public static final String GOOGLE_EARTH_PACKAGE = "com.google.earth";
public static final String GOOGLE_EARTH_CLASS = "com.google.earth.EarthActivity";
private static final String EARTN_MARKET_URI = "market://details?id=" + GOOGLE_EARTH_PACKAGE;
private PlayTrackUtils() {}
/**
* Returns true if a Google Earth app that can handle KML mine type is
* installed.
*
* @param context the context
*/
public static boolean isEarthInstalled(Context context) {
List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(
new Intent().setType(KML_MIME_TYPE), PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : infos) {
if (info.activityInfo != null && info.activityInfo.packageName != null
&& info.activityInfo.packageName.equals(GOOGLE_EARTH_PACKAGE)) {
return true;
}
}
return false;
}
/**
* Plays a track by sending an intent to {@link SaveActivity}.
*
* @param context the context
* @param trackId the track id
*/
public static void playTrack(Context context, long trackId) {
AnalyticsUtils.sendPageViews(context, "/action/play");
Intent intent = new Intent(context, SaveActivity.class)
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.KML)
.putExtra(SaveActivity.EXTRA_PLAY_TRACK, true);
context.startActivity(intent);
}
/**
* Creates a dialog to install Google Earth from the Android Market.
*
* @param context the context
*/
public static Dialog createInstallEarthDialog(final Context context) {
return new AlertDialog.Builder(context)
.setCancelable(true)
.setMessage(R.string.track_detail_install_earth_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setData(Uri.parse(EARTN_MARKET_URI));
context.startActivity(intent);
}
})
.create();
}
}
@@ -148,7 +148,7 @@ public class TrackWidgetProvider
Intent intent = new Intent(context, TrackDetailActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (track != null) {
intent.putExtra(TrackDetailActivity.TRACK_ID, track.getId());
intent.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, track.getId());
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.appwidget_track_statistics, pendingIntent);
@@ -13,152 +13,100 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Instrumentation;
import android.location.Location;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.widget.ZoomControls;
import android.test.AndroidTestCase;
/**
* Tests {@link ChartActivity}.
*
* Tests {@link ChartFragment}.
*
* @author Youtao Liu
*/
public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartActivity> {
public class ChartFragmentTest extends AndroidTestCase {
private Instrumentation instrumentation;
private ChartActivity chartActivity;
private View zoomIn;
private View zoomOut;
private int currentZoomLevel;
private ChartFragment chartFragment;
// 10 is same with the default value in ChartView
private final int MAX_ZOOM_LEVEL = 10;
private final int MIN_ZOOM_LEVEL = 1;
// The ratio from meter/second to kilometer/hour, the conversion is 60 * 60 /
// 1000 = 3.6.
private final double METER_PER_SECOND_TO_KILOMETER_PER_HOUR = 3.6;
private final double KILOMETER_TO_METER = 1000.0;
private final double HOURS_PER_UNIT = 60;
public ChartActivityTest() {
super(ChartActivity.class);
}
private final double HOURS_PER_UNIT = 60.0;
@Override
protected void setUp() throws Exception {
super.setUp();
instrumentation = getInstrumentation();
chartActivity = getActivity();
chartFragment = new ChartFragment();
chartFragment.setChartView(new ChartView(getContext()));
}
/**
* Tests zooming in and zooming out.
*/
public void testZoomInAndZoomOut() {
currentZoomLevel = chartActivity.getChartView().getZoomLevel();
chartActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
ZoomControls zoomControls = (ZoomControls) chartActivity.findViewById(R.id.elevation_zoom);
zoomIn = zoomControls.getChildAt(0);
zoomOut = zoomControls.getChildAt(1);
// Invoke following two methods method to initial the display of
// ZoomControls.
zoomControls.setIsZoomInEnabled(chartActivity.getChartView().canZoomIn());
zoomControls.setIsZoomOutEnabled(chartActivity.getChartView().canZoomOut());
// Click zoomIn button to disable.
for (int i = currentZoomLevel; i > MIN_ZOOM_LEVEL; i--) {
zoomIn.performClick();
}
}
});
instrumentation.waitForIdleSync();
assertEquals(false, zoomIn.isEnabled());
assertEquals(true, zoomOut.isEnabled());
chartActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
// Click to the second max zoom level
for (int i = MIN_ZOOM_LEVEL; i < MAX_ZOOM_LEVEL - 1; i++) {
zoomOut.performClick();
assertEquals(true, zoomIn.isEnabled());
assertEquals(true, zoomOut.isEnabled());
}
zoomOut.performClick();
}
});
instrumentation.waitForIdleSync();
assertEquals(true, zoomIn.isEnabled());
assertEquals(false, zoomOut.isEnabled());
}
/**
* Tests the logic to get the incorrect values of sensor in {@link
* ChartActivity#fillDataPoint(Location, double[])}.
* Tests the logic to get the incorrect values of sensor in
* {@link ChartFragment#fillDataPoint(Location, double[])}
*/
public void testFillDataPoint_sensorIncorrect() {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
// No input.
double[] point = fillDataPointTestHelper(myTracksLocation, false);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
// Input incorrect state.
// Creates SensorData.
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.NONE);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
.setHeartRate(heartRateData).build();
sensorDataSet = sensorDataSet.toBuilder()
.setPower(powerData)
.setCadence(cadenceData)
.setHeartRate(heartRateData)
.build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
point = fillDataPointTestHelper(myTracksLocation, false);
point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
}
/**
* Tests the logic to get the correct values of sensor in {@link
* ChartActivity#fillDataPoint(Location, double[])}.
* Tests the logic to get the correct values of sensor in
* {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
public void testFillDataPoint_sensorCorrect() {
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
// No input.
double[] point = fillDataPointTestHelper(myTracksLocation, false);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(Double.NaN, point[3]);
assertEquals(Double.NaN, point[4]);
assertEquals(Double.NaN, point[5]);
// Creates SensorData.
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder().setValue(20)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder powerData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder cadenceData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
Sensor.SensorData.Builder heartRateData = Sensor.SensorData.newBuilder()
.setValue(20).setState(Sensor.SensorState.SENDING);
// Creates SensorDataSet.
SensorDataSet sensorDataSet = myTracksLocation.getSensorDataSet();
sensorDataSet = sensorDataSet.toBuilder().setPower(powerData).setCadence(cadenceData)
.setHeartRate(heartRateData).build();
sensorDataSet = sensorDataSet.toBuilder()
.setPower(powerData)
.setCadence(cadenceData)
.setHeartRate(heartRateData)
.build();
myTracksLocation.setSensorData(sensorDataSet);
// Test.
point = fillDataPointTestHelper(myTracksLocation, false);
point = fillDataPointTestHelper(myTracksLocation);
assertEquals(20.0, point[3]);
assertEquals(20.0, point[4]);
assertEquals(20.0, point[5]);
@@ -166,61 +114,63 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
/**
* Tests the logic to get the value of metric Distance in
* {@link ChartActivity#fillDataPoint(Location, double[])}.
* {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
public void testFillDataPoint_distanceMetric() {
// By distance.
chartActivity.getChartView().setMode(ChartView.Mode.BY_DISTANCE);
chartFragment.getChartView().setMode(ChartView.Mode.BY_DISTANCE);
// Resets last location and writes first location.
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[0]);
// The second is a same location, just different time.
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
point = fillDataPointTestHelper(myTracksLocation2, false);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(0.0, point[0]);
// The third location is a new location, and use metric.
MyTracksLocation myTracksLocation3 = TrackStubUtils.createMyTracksLocation();
myTracksLocation3.setLatitude(23);
point = fillDataPointTestHelper(myTracksLocation3, false);
point = fillDataPointTestHelper(myTracksLocation3);
// Computes the distance between Latitude 22 and 23.
float[] results = new float[4];
Location.distanceBetween(myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(),
myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(), results);
double distance1 = results[0];
assertEquals(distance1 / KILOMETER_TO_METER, point[0]);
double distance1 = results[0] * UnitConversions.M_TO_KM;
assertEquals(distance1, point[0]);
// The fourth location is a new location, and use metric.
MyTracksLocation myTracksLocation4 = TrackStubUtils.createMyTracksLocation();
myTracksLocation4.setLatitude(24);
point = fillDataPointTestHelper(myTracksLocation4, false);
point = fillDataPointTestHelper(myTracksLocation4);
// Computes the distance between Latitude 23 and 24.
Location.distanceBetween(myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(),
myTracksLocation4.getLatitude(), myTracksLocation4.getLongitude(), results);
double distance2 = results[0];
assertEquals((distance1 + distance2) / KILOMETER_TO_METER, point[0]);
double distance2 = results[0] * UnitConversions.M_TO_KM;
assertEquals((distance1 + distance2), point[0]);
}
/**
* Tests the logic to get the value of imperial Distance in
* {@link ChartActivity#fillDataPoint(Location, double[])}.
* {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
public void testFillDataPoint_distanceImperial() {
// Setups to use imperial.
chartActivity.onUnitsChanged(false);
chartFragment.setMetricUnits(false);
// The first is a same location, just different time.
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[0]);
// The second location is a new location, and use imperial.
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
myTracksLocation2.setLatitude(23);
point = fillDataPointTestHelper(myTracksLocation2, false);
point = fillDataPointTestHelper(myTracksLocation2);
/*
* Computes the distance between Latitude 22 and 23. And for we set using
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
@@ -228,78 +178,84 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
float[] results = new float[4];
Location.distanceBetween(myTracksLocation1.getLatitude(), myTracksLocation1.getLongitude(),
myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(), results);
double distance1 = results[0] * UnitConversions.KM_TO_MI;
assertEquals(distance1 / KILOMETER_TO_METER, point[0]);
double distance1 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
assertEquals(distance1, point[0]);
// The third location is a new location, and use imperial.
MyTracksLocation myTracksLocation3 = TrackStubUtils.createMyTracksLocation();
myTracksLocation3.setLatitude(24);
point = fillDataPointTestHelper(myTracksLocation3, false);
point = fillDataPointTestHelper(myTracksLocation3);
/*
* Computes the distance between Latitude 23 and 24. And for we set using
* imperial, the distance should be multiplied by UnitConversions.KM_TO_MI.
*/
Location.distanceBetween(myTracksLocation2.getLatitude(), myTracksLocation2.getLongitude(),
myTracksLocation3.getLatitude(), myTracksLocation3.getLongitude(), results);
double distance2 = results[0] * UnitConversions.KM_TO_MI;
assertEquals(distance1 / KILOMETER_TO_METER + distance2 / KILOMETER_TO_METER , point[0]);
double distance2 = results[0] * UnitConversions.M_TO_KM * UnitConversions.KM_TO_MI;
assertEquals(distance1 + distance2, point[0]);
}
/**
* Tests the logic to get the values of time in {@link ChartActivity#fillDataPoint(Location, double[])}.
* Tests the logic to get the values of time in
* {@link ChartFragment#fillDataPoint(Location, double[])}.
*/
public void testFillDataPoint_time() {
// By time
chartActivity.getChartView().setMode(ChartView.Mode.BY_TIME);
chartFragment.getChartView().setMode(ChartView.Mode.BY_TIME);
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[0]);
long timeSpan = 222;
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
myTracksLocation2.setTime(myTracksLocation1.getTime() + timeSpan);
point = fillDataPointTestHelper(myTracksLocation2, false);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals((double) timeSpan, point[0]);
}
/**
* Tests the logic to get the value of elevation in
* {@link ChartActivity#fillDataPoint(Location, double[])} by one and two points.
* {@link ChartFragment#fillDataPoint(Location, double[])} by one and two
* points.
*/
public void testFillDataPoint_elevation() {
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
/*
* At first, clear old points of elevation, so give true to the second
* parameter. Then only one value INITIALLONGTITUDE in buffer.
*/
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(TrackStubUtils.INITIAL_ALTITUDE, point[1]);
/*
* Send another value to buffer, now there are two values, INITIALALTITUDE
* and INITIALALTITUDE * 2.
*/
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
myTracksLocation2.setAltitude(TrackStubUtils.INITIAL_ALTITUDE * 2);
point = fillDataPointTestHelper(myTracksLocation2, false);
assertEquals((TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(
(TrackStubUtils.INITIAL_ALTITUDE + TrackStubUtils.INITIAL_ALTITUDE * 2) / 2.0, point[1]);
}
/**
* Tests the logic to get the value of speed in
* {@link ChartActivity#fillDataPoint(Location, double[])}. In this test, firstly remove all
* points in memory, and then fill in two points one by one. The speed values
* of these points are 129, 130.
* {@link ChartFragment#fillDataPoint(Location, double[])}. In this test,
* firstly remove all points in memory, and then fill in two points one by
* one. The speed values of these points are 129, 130.
*/
public void testFillDataPoint_speed() {
// Set max speed to make the speed of points are valid.
chartActivity.setTrackMaxSpeed(200.0);
chartFragment.setTrackMaxSpeed(200.0);
/*
* At first, clear old points of speed, so give true to the second
* parameter. It will not be filled in to the speed buffer.
*/
MyTracksLocation myTracksLocation1 = TrackStubUtils.createMyTracksLocation();
myTracksLocation1.setSpeed(129);
double[] point = fillDataPointTestHelper(myTracksLocation1, true);
double[] point = fillDataPointTestHelper(myTracksLocation1);
assertEquals(0.0, point[2]);
/*
@@ -307,12 +263,15 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
* location will be filled into speed buffer.
*/
MyTracksLocation myTracksLocation2 = TrackStubUtils.createMyTracksLocation();
// Add a time span here to make sure the second point is valid, the value
// 222 here is doesn't matter.
/*
* Add a time span here to make sure the second point is valid, the value
* 222 here is doesn't matter.
*/
myTracksLocation2.setTime(myTracksLocation1.getTime() + 222);
myTracksLocation2.setSpeed(130);
point = fillDataPointTestHelper(myTracksLocation2, false);
assertEquals(130.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR, point[2]);
point = fillDataPointTestHelper(myTracksLocation2);
assertEquals(130.0 * UnitConversions.MS_TO_KMH, point[2]);
}
/**
@@ -320,12 +279,11 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
*/
public void testFillDataPoint_speedImperial() {
// Setups to use imperial.
chartActivity.onUnitsChanged(false);
chartFragment.setMetricUnits(false);
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(132);
double[] point = fillDataPointTestHelper(myTracksLocation, true);
assertEquals(132.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR * UnitConversions.KM_TO_MI,
point[2]);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(132.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point[2]);
}
/**
@@ -333,11 +291,11 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
*/
public void testFillDataPoint_pace_nonZeroSpeed() {
// Setups reportSpeed to false.
chartActivity.onReportSpeedChanged(false);
chartFragment.setReportSpeed(false);
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(134);
double[] point = fillDataPointTestHelper(myTracksLocation, true);
assertEquals(HOURS_PER_UNIT / (134.0 * METER_PER_SECOND_TO_KILOMETER_PER_HOUR), point[2]);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(HOURS_PER_UNIT / (134.0 * UnitConversions.MS_TO_KMH), point[2]);
}
@@ -347,26 +305,22 @@ public class ChartActivityTest extends ActivityInstrumentationTestCase2<ChartAct
*/
public void testFillDataPoint_pace_zeroSpeed() {
// Setups reportSpeed to false.
chartActivity.onReportSpeedChanged(false);
chartFragment.setReportSpeed(false);
MyTracksLocation myTracksLocation = TrackStubUtils.createMyTracksLocation();
myTracksLocation.setSpeed(0);
double[] point = fillDataPointTestHelper(myTracksLocation, true);
assertEquals(Double.NaN, point[2]);
double[] point = fillDataPointTestHelper(myTracksLocation);
assertEquals(0.0, point[2]);
}
/**
* Helper method to test fillDataPoint.
*
*
* @param location location to fill
* @param isNeedClear true to clear the track points
* @return data of this location
*/
private double[] fillDataPointTestHelper(Location location, boolean isNeedClear) {
if (isNeedClear) {
chartActivity.clearTrackPoints();
}
private double[] fillDataPointTestHelper(Location location) {
double[] point = new double[6];
chartActivity.fillDataPoint(location, point);
chartFragment.fillDataPoint(location, point);
return point;
}