Merging gingerbread changes to head.

This commit is contained in:
Rodrigo Damazio
2010-12-10 14:30:29 -02:00
parent 93b95954c4
commit 19d5fe1ba6
111 changed files with 5041 additions and 837 deletions
+2
View File
@@ -6,5 +6,7 @@
<classpathentry kind="lib" path="libs/accounts.jar"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="src" path="MyTracksLib_src"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+7
View File
@@ -30,4 +30,11 @@
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<linkedResources>
<link>
<name>MyTracksLib_src</name>
<type>2</type>
<locationURI>_android_MyTracksLib_e2802071/src</locationURI>
</link>
</linkedResources>
</projectDescription>
+64 -10
View File
@@ -1,9 +1,24 @@
<?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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.maps.mytracks"
android:installLocation="auto"
android:versionCode="20"
android:versionName="1.0.19">
android:versionCode="22"
android:versionName="1.0.21">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
@@ -13,7 +28,10 @@
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
android:authorities="com.google.android.maps.mytracks" />
android:authorities="com.google.android.maps.mytracks"
android:exported="true"
android:readPermission="com.google.android.apps.mytracks.READ_TRACK_DATA"
android:writePermission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<activity android:name="com.google.android.apps.mytracks.MyTracks"
android:label="My Tracks"
@@ -35,11 +53,13 @@
</activity>
<activity android:name="com.google.android.apps.mytracks.MyTracksMap"
android:label="MyTracks" />
android:label="MyTracks"
android:exported="false" />
<activity android:name="com.google.android.apps.mymaps.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale" />
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.MyTracksList"
android:label="Recorded Tracks" />
@@ -60,19 +80,26 @@
android:label="@string/aggregated_stats" />
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
android:label="@string/statistics" />
android:label="@string/statistics"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
android:label="@string/sensor_state" />
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
android:label="@string/elevation_label"/>
android:label="@string/elevation_label"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
android:label="@string/welcome"/>
android:label="@string/welcome"
android:exported="false" />
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
android:label="Track Recording Service"
android:icon="@drawable/arrow_320"
android:enabled="true"
android:exported="true" />
android:exported="true"
android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<receiver android:name="com.google.android.apps.mytracks.MyTracksReceiver">
<intent-filter>
@@ -89,6 +116,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Permissions for uploading tracks -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
@@ -102,7 +131,32 @@
<!-- Permissions for misc services -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- Exported permissions (for other apps to use our features) -->
<!-- Permission to get track start/end broadcasts -->
<permission android:name="com.google.android.apps.mytracks.TRACK_NOTIFICATIONS"
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/notification_permission_label"
android:description="@string/notification_permission_description"
android:protectionLevel="normal" />
<!-- Permission to read raw track data (points, etc.) -->
<permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA"
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/read_tracks_permission_label"
android:description="@string/read_tracks_permission_description"
android:protectionLevel="dangerous" />
<!-- Permission to write track data, either raw or by making service calls -->
<permission android:name="com.google.android.apps.mytracks.WRITE_TRACK_DATA"
android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/write_tracks_permission_label"
android:description="@string/write_tracks_permission_description"
android:protectionLevel="dangerous" />
<!-- We read and write raw track data, obviously. -->
<uses-permission android:name="com.google.android.apps.mytracks.READ_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
<uses-permission android:name="com.google.android.apps.mytracks.TRACK_NOTIFICATIONS" />
<!-- Features required -->
<uses-feature android:name="android.hardware.location" android:required="true"/>
<uses-feature android:name="android.hardware.location.gps" android:required="true"/>
+1
View File
@@ -11,3 +11,4 @@
split.density=false
# Project target.
target=Google Inc.:Google APIs:9
android.library.reference.1=../MyTracksLib/
+34
View File
@@ -0,0 +1,34 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+15
View File
@@ -103,6 +103,21 @@ limitations under the License.
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/speed"/>
<CheckBox android:id="@+id/chart_settings_power"
android:checked="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/power"/>
<CheckBox android:id="@+id/chart_settings_cadence"
android:checked="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/cadence"/>
<CheckBox android:id="@+id/chart_settings_heart_rate"
android:checked="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/heart_rate"/>
</LinearLayout>
</TableRow>
</TableLayout>
+131
View File
@@ -0,0 +1,131 @@
<?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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_width="fill_parent">
<ScrollView
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent">
<TableLayout
android:layout_height="wrap_content"
android:stretchColumns="1,4"
android:layout_width="fill_parent">
<!-- Layout the sensor state on one line. -->
<TableRow>
<TextView
android:layout_span="5"
android:text="@string/sensor_state" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
android:id="@+id/sensor_state_register"
android:layout_column="1"
android:layout_span="5"
style="@style/StatsRegister" />
</TableRow>
<!-- Layout the sensor data age on one line. -->
<TableRow>
<TextView
android:layout_span="5"
android:text="@string/sensor_data_time" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
android:id="@+id/sensor_data_time_register"
android:layout_column="1"
android:layout_span="5"
style="@style/StatsRegister" />
</TableRow>
<!-- Power meter -->
<TableRow
android:padding="1dip">
<TextView
android:layout_column="1"
android:layout_span="3"
android:text="@string/power" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
style="@style/StatsRegister.Big"
android:id="@+id/power_state_register"
android:layout_column="1"
android:layout_span="4" />
<TextView
style="@style/StatsUnitSolo" />
android:layout_column="5"
android:text="@string/watts" />
</TableRow>
<!-- Cadence -->
<TableRow
android:padding="1dip">
<TextView
android:layout_column="1"
android:layout_span="3"
android:text="@string/cadence" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
style="@style/StatsRegister.Big"
android:id="@+id/cadence_state_register"
android:layout_column="4" />
<TextView
style="@style/StatsUnitSolo" />
android:layout_column="2"
android:text="@string/revolutions_per_minute" />
</TableRow>
<!-- Heart Rate -->
<TableRow
android:padding="1dip">
<TextView
android:layout_column="1"
android:layout_span="3"
android:text="@string/heart_rate" />
</TableRow>
<TableRow
android:background="@color/stat_label_background">
<TextView
style="@style/StatsRegister.Big"
android:id="@+id/heart_rate_register"
android:layout_column="1"
android:layout_span="4" />
<TextView
style="@style/StatsUnitSolo"
android:layout_column="5"
android:text="@string/bpm" />
</TableRow>
</TableLayout>
</LinearLayout>
</ScrollView>
</RelativeLayout>
+48 -225
View File
@@ -42,15 +42,10 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
style="@style/StatsRegister.Big"
android:id="@+id/speed_register"
android:layout_column="1"
android:layout_marginBottom="-13dip"
android:layout_marginTop="-13dip"
android:layout_span="4"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="50sp" />
android:layout_span="4"/>
<LinearLayout
android:gravity="bottom"
android:layout_column="5"
@@ -62,24 +57,12 @@ limitations under the License.
android:layout_width="fill_parent" />
<TextView
android:id="@+id/speed_unit_label_top"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="2dip"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
<TextView
android:background="@color/black"
android:layout_height="3dip"
android:layout_width="fill_parent" />
style="@style/StatsDivider" />
<TextView
android:id="@+id/speed_unit_label_bottom"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="2dip"
android:layout_marginTop="2dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
@@ -100,43 +83,25 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
style="@style/StatsRegister"
android:id="@+id/total_distance_register"
android:layout_column="1"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
android:layout_column="1" />
<TextView
android:gravity="left"
android:id="@+id/total_distance_unit_label"
android:layout_column="2"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/mile"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnitSolo" />
<!-- TODO 4/08/2009
The following is a bad hack to get a spacer the same size as the
text. Ideally this should just be a View, however I don't know how to
set the view height properly. fill_parent on this element causes as
NullPointerException. This same filler is repeated on each line.-->
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/max_speed_register"
android:layout_column="4"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister"
android:layout_column="4" />
<LinearLayout
android:gravity="bottom"
android:layout_column="5"
@@ -145,24 +110,12 @@ limitations under the License.
android:orientation="vertical">
<TextView
android:id="@+id/max_speed_unit_label_top"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
<TextView
android:background="@color/black"
android:layout_height="3dip"
android:layout_width="fill_parent" />
style="@style/StatsDivider" />
<TextView
android:id="@+id/max_speed_unit_label_bottom"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
@@ -182,33 +135,19 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/total_time_register"
android:layout_column="1"
android:layout_height="wrap_content"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_span="2"
android:layout_width="fill_parent"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/average_speed_register"
android:layout_column="4"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<LinearLayout
android:gravity="bottom"
android:layout_column="5"
@@ -217,24 +156,12 @@ limitations under the License.
android:orientation="vertical">
<TextView
android:id="@+id/average_speed_unit_label_top"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
<TextView
android:background="@color/black"
android:layout_height="3dip"
android:layout_width="fill_parent" />
style="@style/StatsDivider" />
<TextView
android:id="@+id/average_speed_unit_label_bottom"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
@@ -254,33 +181,18 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/moving_time_register"
android:layout_column="1"
android:layout_height="wrap_content"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_span="2"
android:layout_width="fill_parent"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/average_moving_speed_register"
android:layout_column="4"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<LinearLayout
android:gravity="bottom"
android:layout_column="5"
@@ -289,24 +201,14 @@ limitations under the License.
android:orientation="vertical">
<TextView
android:id="@+id/average_moving_speed_unit_label_top"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
<TextView
android:background="@color/black"
style="@style/StatsDivider" />
android:layout_height="3dip"
android:layout_width="fill_parent" />
<TextView
android:id="@+id/average_moving_speed_unit_label_bottom"
android:gravity="bottom"
android:layout_height="wrap_content"
android:layout_marginBottom="-1dip"
android:layout_marginTop="-1dip"
android:layout_width="wrap_content"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnit" />
</LinearLayout>
</TableRow>
@@ -326,46 +228,24 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/elevation_register"
android:layout_column="1"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:gravity="left"
android:id="@+id/elevation_unit_label"
android:layout_column="2"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/feet"
android:textColor="@color/stat_label_text"/>
style="@style/StatsUnitSolo" />
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/elevation_gain_register"
android:layout_column="4"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:gravity="right"
android:id="@+id/elevation_gain_unit_label"
android:layout_column="5"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/feet"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnitSolo" />
</TableRow>
<!-- Min and max elevation -->
@@ -384,46 +264,24 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/min_elevation_register"
android:layout_column="1"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:gravity="left"
android:id="@+id/min_elevation_unit_label"
android:layout_column="2"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/feet"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnitSolo" />
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/max_elevation_register"
android:layout_column="4"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:gravity="right"
android:id="@+id/max_elevation_unit_label"
android:layout_column="5"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:text="@string/feet"
android:textColor="@color/stat_label_text" />
style="@style/StatsUnitSolo" />
</TableRow>
<!-- Min and max grade -->
@@ -442,36 +300,18 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/min_grade_register"
android:layout_column="1"
android:layout_height="wrap_content"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="fill_parent"
android:layout_span="2"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
<TextView
android:background="@color/black"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="40sp" />
style="@style/StatsSpacer"
android:layout_column="3"/>
<TextView
android:gravity="right"
android:id="@+id/max_grade_register"
android:layout_column="4"
android:layout_height="wrap_content"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="fill_parent"
android:layout_span="2"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="40sp" />
style="@style/StatsRegister" />
</TableRow>
<!-- Latitude and Longitude -->
@@ -488,36 +328,19 @@ limitations under the License.
<TableRow
android:background="@color/stat_label_background">
<TextView
android:gravity="right"
android:id="@+id/latitude_register"
android:layout_column="1"
android:layout_height="wrap_content"
android:layout_marginBottom="-5dip"
android:layout_marginTop="-5dip"
android:layout_width="fill_parent"
android:layout_span="2"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="25sp" />
style="@style/StatsRegister.Small" />
<TextView
android:background="@color/black"
style="@style/StatsSpacer"
android:layout_column="3"
android:layout_marginBottom="-9dip"
android:layout_marginTop="-9dip"
android:layout_width="5dip"
android:textSize="25sp" />
<TextView
android:gravity="right"
android:id="@+id/longitude_register"
android:layout_column="4"
android:layout_height="wrap_content"
android:layout_marginBottom="-5dip"
android:layout_marginTop="-5dip"
android:layout_width="fill_parent"
android:layout_span="2"
android:text="@string/unknown"
android:textColor="@color/stat_label_text"
android:textSize="25sp" />
style="@style/StatsRegister.Small" />
</TableRow>
</TableLayout>
+5 -1
View File
@@ -22,10 +22,14 @@
android:title="@string/settings"/>
<item android:id="@+id/menu_aggregated_stats"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="1002"
android:title="@string/aggregated_stats"/>
<item android:id="@+id/menu_sensor_state"
android:icon="@drawable/menu_sensor_status"
android:orderInCategory="1000"
android:title="@string/sensor_state"/>
<item android:id="@+id/menu_help"
android:icon="@android:drawable/ic_menu_info_details"
android:orderInCategory="1003"
-3
View File
@@ -184,9 +184,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Beim teilen mit Freunden wird nur die Karten URL gesendet.</string>
<string name="settings_share_url_only_summary_off">Beim teilen mit Freunden wird der GPS Track selbst gesendet.</string>
<string name="settings_signal_sampling_frequency">Empfangsstärke Aufzeichnungsfrequenz</string>
<string name="settings_signal_sampling_frequency_summary">Dauer zwischen zwei Aufzeichnungen der Telefonempfangsstärke.</string>
<string name="settings_split_frequency">Zerteilungs Frequenz</string>
<string name="settings_split_frequency_summary">Längste Dauer oder Entfernung nach der automatisch ein Marker eingefügt wird.</string>
<string name="pace_label">Tempo</string>
-3
View File
@@ -187,9 +187,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Sólo el URL del mapa será enviado al compartir</string>
<string name="settings_share_url_only_summary_off">Los contenidos de la pista también serán incluídos al compartir</string>
<string name="settings_signal_sampling_frequency">Frecuencia de medición de señal</string>
<string name="settings_signal_sampling_frequency_summary">Tiempo entre grabaciones de la intensidad de la señal del teléfono</string>
<string name="settings_split_frequency">Dividir frecuencia</string>
<string name="settings_split_frequency_summary">Tiempo o distancia máxima para insertar una nueva marca de división</string>
<string name="pace_label">Paso</string>
-3
View File
@@ -187,9 +187,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Seule l\'adresse URL de la carte sera envoyée lors du partage</string>
<string name="settings_share_url_only_summary_off">Le contenu du parcours sera également inclus lors du partage</string>
<string name="settings_signal_sampling_frequency">Échantillonnage du signal</string>
<string name="settings_signal_sampling_frequency_summary">Intervalle de temps entre deux enregistrements de la force du signal</string>
<string name="settings_split_frequency">Fréquence de séparation</string>
<string name="settings_split_frequency_summary">Temps ou distance minimum avant l\'insertion d\'un marqueur de séparation</string>
<string name="pace_label">Rythme</string>
-3
View File
@@ -193,9 +193,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Solo l\'URL della mappa sarà inviata quando condividi un percorso</string>
<string name="settings_share_url_only_summary_off">Anche il contenuto del percorso sarà inviato quando condividi un persorso</string>
<string name="settings_signal_sampling_frequency">Frequenza di campionamento</string>
<string name="settings_signal_sampling_frequency_summary">Tempo fra due registrazioni della qualità del segnale cellulare</string>
<string name="settings_split_frequency">Frequenza di divisione</string>
<string name="settings_split_frequency_summary">Tempo o distanza massima dopo di cui il percorso verrà diviso</string>
<string name="pace_label">Andatura</string>
-11
View File
@@ -194,9 +194,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">共有時にマップのURLのみ送信します</string>
<string name="settings_share_url_only_summary_off">共有時にトラックのコンテントが含まれます</string>
<string name="settings_signal_sampling_frequency">信号サンプリング周波数</string>
<string name="settings_signal_sampling_frequency_summary">端末の信号強度記録の時間間隔</string>
<string name="settings_split_frequency">分割頻度</string>
<string name="settings_split_frequency_summary">分割マーカを挿入する時間または距離の間隔</string>
<string name="pace_label">ペース</string>
@@ -384,12 +381,4 @@ limitations under the License.
<!-- Check Units -->
<string name="check_units_title">計測単位についてのご注意</string>
<string name="check_units_message">デフォルトの単位はメートルです.設定を変更しますか?</string>
<!-- Signal Strength -->
<string name="error_rate"> 誤り率: %1$d</string>
<string name="signal_to_noise_ratio"> S/N比: %1$d</string>
<string name="ecio"> Ec/Io比: %1$.1f dB</string>
<string name="gsm_strength">GSM 強度: %1$d</string>
<string name="cdma_strength">CDMA 強度: %1$d dBm</string>
<string name="evdo_strength">EVDO 強度: %1$d dBm</string>
</resources>
-11
View File
@@ -195,9 +195,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Tylko odnośnik do mapy będzie wysyłany podczas dzielenia się</string>
<string name="settings_share_url_only_summary_off">Zawartość trasy będzie również przesyłana</string>
<string name="settings_signal_sampling_frequency">Częstotliowść próbkowania sygnału</string>
<string name="settings_signal_sampling_frequency_summary">Czas między dwoma zapisami siły sygnału telefonu</string>
<string name="settings_split_frequency">Częstotliwość rozdzielania</string>
<string name="settings_split_frequency_summary">Maksymalny czas lub odległość po którym wstawiony zostanie znacznik rozdzielenia</string>
@@ -416,12 +413,4 @@ limitations under the License.
<!-- Check Units -->
<string name="check_units_title">Jednostki miary</string>
<string name="check_units_message">Domyślnie używane są jednostki metryczne. Chcesz to zmienić?</string>
<!-- Signal Strength -->
<string name="error_rate"> Poziom błędów: %1$d</string>
<string name="signal_to_noise_ratio"> Stosunek sygnału do szumu: %1$d</string>
<string name="ecio"> Ec/lo: %1$.1f dB</string>
<string name="gsm_strength">Siła GSM: %1$d</string>
<string name="cdma_strength">Siła CDMA: %1$d dBm</string>
<string name="evdo_strength">Siła EVDO: %1$d dBm</string>
</resources>
-11
View File
@@ -195,9 +195,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Bara länken till kartan kommer att skickas vid delning</string>
<string name="settings_share_url_only_summary_off">Innehållet i spåret kommer också att inkluderas vid delning</string>
<string name="settings_signal_sampling_frequency">Signalprovsfrekvens</string>
<string name="settings_signal_sampling_frequency_summary">Tid mellan två inspelningar av telefonens signalstyrka</string>
<string name="settings_split_frequency">Delningsfrekvens</string>
<string name="settings_split_frequency_summary">Maximala tiden eller distansen varefter en ny delningsmarkör kommer infogas</string>
<string name="pace_label">Tempo</string>
@@ -383,12 +380,4 @@ limitations under the License.
<!-- Check Units -->
<string name="check_units_title">Mätenheter</string>
<string name="check_units_message">Som standard är enheterna metriska. Vill du ändra den inställningen?</string>
<!-- Signal Strength -->
<string name="error_rate"> Felfrekvens: %1$d</string>
<string name="signal_to_noise_ratio"> Signal / brus-förhållande: %1$d</string>
<string name="ecio"> Ec/lo: %1$.1f dB</string>
<string name="gsm_strength">GSM-styrka: %1$d</string>
<string name="cdma_strength">CDMA-styrka: %1$d dBm</string>
<string name="evdo_strength">EVDO-styrka: %1$d dBm</string>
</resources>
-17
View File
@@ -201,9 +201,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">在分享时只发送地图的 URL</string>
<string name="settings_share_url_only_summary_off">在分享时也包括轨迹的内容</string>
<string name="settings_signal_sampling_frequency">信号采样频率</string>
<string name="settings_signal_sampling_frequency_summary">记录电话信号强度的时间间隔</string>
<string name="settings_split_frequency">切割频度</string>
<string name="settings_split_frequency_summary">插入新切割标记所需要达到的最大时间或距离</string>
@@ -451,14 +448,6 @@ limitations under the License.
<string name="check_units_title">测量单位提示</string>
<string name="check_units_message">默认单位均为公制. 你想要修改此设置吗?</string>
<!-- Signal Strength -->
<string name="error_rate"> 错误率: %1$d</string>
<string name="signal_to_noise_ratio"> 信噪比: %1$d</string>
<string name="ecio"> Ec/lo: %1$.1f dB</string>
<string name="gsm_strength">GSM 强度 %1$d</string>
<string name="cdma_strength">CDMA 强度 %1$d dBm</string>
<string name="evdo_strength">EVDO 强度 %1$d dBm</string>
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
<string name="method_bluetooth">蓝牙</string>
<string name="bluetooth_options">蓝牙设置</string>
@@ -467,12 +456,6 @@ limitations under the License.
<string name="sensor_options">感应器选项</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">连接到 %s</string>
<string name="bluetooth_connecting">蓝牙感应器连接中 ...</string>
+12
View File
@@ -390,4 +390,16 @@ limitations under the License.
<item>CSV</item>
<item>TCX</item>
</array>
<array name="sensor_type_options">
<item>None</item>
<item>Zephyr Bluetooth</item>
</array>
<!-- DO NOT TRANSLATE -->
<array name="sensor_type_values">
<item>@string/none</item>
<item>@string/zephyr_sensor_type</item>
</array>
</resources>
+11 -6
View File
@@ -15,17 +15,22 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="gold">#FFffd700</color>
<color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color>
<color name="lightblue">#FF00FFFF</color>
<color name="blue">#FF0000FF</color>
<color name="blue_transparent">#400000FF</color>
<color name="red">#FFFF0000</color>
<color name="green">#FF00AA00</color>
<color name="gray">#FFAAAAAA</color>
<color name="darkgray">#FF444444</color>
<color name="gold">#FFffd700</color>
<color name="gray">#FFAAAAAA</color>
<color name="green">#FF00AA00</color>
<color name="lightblue">#FF00FFFF</color>
<color name="opaque_shadow">#70000000</color>
<color name="purple">#FF401465</color>
<color name="purple_transparent">#40401465</color>
<color name="red">#FFFF0000</color>
<color name="red_transparent">#40FF0000</color>
<color name="stat_label_background">#FFF0F0F0</color>
<color name="stat_label_text">#FF0F0F0F</color>
<color name="white">#FFFFFFFF</color>
<color name="yellow">#FFF8FF00</color>
<color name="yellow_transparent">#40F8FF00</color>
</resources>
-2
View File
@@ -33,10 +33,8 @@ limitations under the License.
<string name="send_stats_and_points_key">sendStatsAndPoints</string>
<string name="send_to_docs_key">sendToDocs</string>
<string name="send_to_my_maps_key">sendToMyMaps</string>
<string name="sensor_id_key">sensorId</string>
<string name="sensor_type_key">sensorType</string>
<string name="share_url_only_key">shareUrlOnly</string>
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
<string name="split_frequency_key">splitFrequency</string>
<string name="timestamp_track_name_key">timestampTrackName</string>
+17 -14
View File
@@ -32,6 +32,7 @@ limitations under the License.
<string name="choose_track_message">Please select the track to view</string>
<string name="current_track">Current track</string>
<string name="current_segment">Last segment</string>
<string name="sensor_state">Sensor State</string>
<string name="aggregated_stats">Aggregated Statistics</string>
<!-- Error messages -->
@@ -201,9 +202,6 @@ limitations under the License.
<string name="settings_share_url_only_summary_on">Only the URL of the map will be sent when sharing</string>
<string name="settings_share_url_only_summary_off">The contents of the track will also be included when sharing</string>
<string name="settings_signal_sampling_frequency">Signal sampling frequency</string>
<string name="settings_signal_sampling_frequency_summary">Time between two recordings of the phone\'s signal strength</string>
<string name="settings_split_frequency">Split frequency</string>
<string name="settings_split_frequency_summary">Maximum time or distance after which a new split marker will be inserted</string>
@@ -447,14 +445,6 @@ limitations under the License.
<string name="check_units_title">Units of Measure Notice</string>
<string name="check_units_message">By default units are metric. Would you like to change that setting?</string>
<!-- Signal Strength -->
<string name="error_rate"> Error Rate: %1$d</string>
<string name="signal_to_noise_ratio"> Signal to Noise Ratio: %1$d</string>
<string name="ecio"> Ec/lo: %1$.1f dB</string>
<string name="gsm_strength">GSM Strength: %1$d</string>
<string name="cdma_strength">CDMA Strength: %1$d dBm</string>
<string name="evdo_strength">EVDO Strength: %1$d dBm</string>
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
<string name="method_bluetooth">Bluetooth</string>
<string name="bluetooth_options">Bluetooth options</string>
@@ -465,9 +455,7 @@ limitations under the License.
<!-- Sensor Types => DO NOT TRANSLATE -->
<string name="none">NONE</string>
<string name="ant_sensor_type">ANT</string>
<string name="srm_sensor_type">SRM</string>
<string name="athlosoft_sensor_type">ATHLOSOFT</string>
<string name="zephyr_sensor_type">ZEPHYR</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">Connected to %s</string>
@@ -478,5 +466,20 @@ limitations under the License.
<string name="sensor_data_time">Sensor Data Time</string>
<string name="power_meter_enabled">Power meter enabled</string>
<string name="heart_rate_monitor_enabled">Heart rate monitor enabled</string>
<string name="no_sensor_manager">No Sensor Manager</string>
<string name="enabled">Enabled</string>
<string name="connecting">Connecting</string>
<string name="connected">Connected</string>
<string name="disconnected">Disconnected</string>
<string name="no_sensor_service">No Sensor Service</string>
<string name="searching">Searching</string>
<string name="sending">Sending</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">receive track recording notifications</string>
<string name="read_tracks_permission_label">read recorded track data</string>
<string name="write_tracks_permission_label">write recorded track data, start/stop recording tracks</string>
<string name="notification_permission_description">Be notified when a track starts or stops being recorded.</string>
<string name="read_tracks_permission_description">Be able to read track data, such as points and waypoints.</string>
<string name="write_tracks_permission_description">Be able to write track data, such as starting recording or adding waypoints.</string>
</resources>
+77
View File
@@ -0,0 +1,77 @@
<?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>
<!-- The style for the unit on the statistics activity. -->
<style name="StatsUnit" parent="@android:Widget.TextView">
<item name="android:gravity">bottom</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">-2dip</item>
<item name="android:layout_marginTop">-2dip</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textColor">@color/stat_label_text</item>
</style>
<!-- The style for the unit on the statistics activity where there is only one string. -->
<style name="StatsUnitSolo" parent="@android:Widget.TextView">
<item name="android:gravity">right</item>
<item name="android:layout_marginBottom">-9dip</item>
<item name="android:layout_marginTop">-9dip</item>
<item name="android:textColor">@color/stat_label_text</item>
</style>
<!-- The style for a text view for displaying a value on the statistics activity. -->
<style name="StatsRegister" parent="@android:Widget.TextView">
<item name="android:gravity">right</item>
<item name="android:layout_marginBottom">-9dip</item>
<item name="android:layout_marginTop">-9dip</item>
<item name="android:text">@string/unknown</item>
<item name="android:textColor">@color/stat_label_text</item>
<item name="android:textSize">40sp</item>
</style>
<!-- The style for a text view for displaying a large value on the statistics activity. -->
<style name="StatsRegister.Big">
<item name="android:layout_marginBottom">-13dip</item>
<item name="android:layout_marginTop">-13dip</item>
<item name="android:textSize">50sp</item>
</style>
<!-- The style for a text view for displaying a small value on the statistics activity. -->
<style name="StatsRegister.Small">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginBottom">-5dip</item>
<item name="android:layout_marginTop">-5dip</item>
<item name="android:layout_width">fill_parent</item>
<item name="android:textSize">25sp</item>
</style>
<!-- The style for a view which divides two columns in the statistics activity. -->
<style name="StatsSpacer" parent="@android:Widget.TextView">
<item name="android:background">@color/black</item>
<item name="android:layout_marginBottom">-9dip</item>
<item name="android:layout_marginTop">-9dip</item>
<item name="android:layout_width">5dip</item>
<item name="android:textSize">40sp</item>
</style>
<!-- The style for a view which divides the numerator and denominator of the units on the statistics activity. -->
<style name="StatsDivider" parent="@android:Widget.TextView">
<item name="android:background">@color/black</item>
<item name="android:layout_height">3dip</item>
<item name="android:layout_width">fill_parent</item>
</style>
</resources>
+23 -8
View File
@@ -84,14 +84,6 @@ limitations under the License.
android:entryValues="@array/min_required_accuracy_values"
android:defaultValue="200" />
<com.google.android.apps.mytracks.IntegerListPreference
android:key="@string/signal_sampling_frequency_key"
android:title="@string/settings_signal_sampling_frequency"
android:summary="@string/settings_signal_sampling_frequency_summary"
android:entries="@array/task_frequency_options"
android:entryValues="@array/task_frequency_values"
android:defaultValue="-1" />
<com.google.android.apps.mytracks.IntegerListPreference
android:key="@string/auto_resume_track_timeout_key"
android:title="@string/settings_auto_resume_track_timeout"
@@ -109,6 +101,29 @@ limitations under the License.
</PreferenceScreen>
<!-- Sensors -->
<PreferenceScreen android:title="@string/sensor_options">
<ListPreference android:key="@string/sensor_type_key"
android:title="@string/settings_sensor_type"
android:summary="@string/settings_sensor_type_summary"
android:entries="@array/sensor_type_options"
android:entryValues="@array/sensor_type_values"
android:defaultValue="@string/none"/>
<!-- Bluetooth -->
<PreferenceCategory android:title="@string/bluetooth_options">
<ListPreference android:key="@string/bluetooth_sensor_key"
android:title="@string/bluetooth_sensor_title"
android:summary="@string/bluetooth_sensor_summary"
android:defaultValue="any" />
<Preference android:key="@string/bluetooth_pairing_key"
android:title="@string/bluetooth_pairing"
android:summary="@string/bluetooth_pairing_summary"
android:persistent="false" />
</PreferenceCategory>
</PreferenceScreen>
<!-- Sharing settings -->
<PreferenceScreen android:title="@string/settings_sharing">
<CheckBoxPreference
@@ -16,11 +16,14 @@
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.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.DoubleBuffer;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
@@ -178,12 +181,12 @@ public class ChartActivity extends Activity implements
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
ArrayList<double[]> data = new ArrayList<double[]>();
// Need two locations so we can keep track of the last location.
Location location = new Location("");
Location location = new MyTracksLocation("");
do {
lastSeenLocationId = cursor.getLong(idColumnIdx);
providerUtils.fillLocation(cursor, location);
if (MyTracksUtils.isValidLocation(location)) {
double[] point = new double[3];
double[] point = new double[6];
location = getDataPoint(location, track, point);
data.add(point);
}
@@ -464,10 +467,14 @@ public class ChartActivity extends Activity implements
/**
* Given a location, creates a new data point for the chart. A data point is
* an array double[2], where:
* 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.
*
@@ -477,6 +484,35 @@ public class ChartActivity extends Activity implements
* @return the previous location, now available for reuse
*/
private Location getDataPoint(Location location, Track track, double[] result) {
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()) {
result[3] = sensorData.getPower().getValue();
} else {
result[3] = Double.NaN;
}
if (sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
result[4] = sensorData.getCadence().getValue();
} else {
result[4] = Double.NaN;
}
if (sensorData.hasHeartRate()
&& sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorData.getHeartRate().hasValue()) {
result[5] = sensorData.getHeartRate().getValue();
} else {
result[5] = Double.NaN;
}
} else {
result[3] = Double.NaN;
result[4] = Double.NaN;
result[5] = Double.NaN;
}
switch (mode) {
case BY_DISTANCE:
result[0] = profileLength;
@@ -529,7 +565,7 @@ public class ChartActivity extends Activity implements
if (oldLastLocation == null) {
// No previous location, but return a blank one for reuse
return new Location("");
return new MyTracksLocation("");
}
return oldLastLocation;
@@ -576,7 +612,7 @@ public class ChartActivity extends Activity implements
final ArrayList<double[]> theData = new ArrayList<double[]>();
int points = 0;
// Need two locations so we can keep track of the last location.
Location location = new Location("");
Location location = new MyTracksLocation("");
while (lastLocationRead < track.getStopId()) {
cursor = providerUtils.getLocationsCursor(
selectedTrackId, lastLocationRead, bufferSize, false);
@@ -592,7 +628,8 @@ public class ChartActivity extends Activity implements
if (MyTracksUtils.isValidLocation(location)) {
lastLocationRead = lastSeenLocationId =
cursor.getLong(idColumnIdx);
double[] point = new double[3];
// TODO Can we be smarter about choosing 3 or 6 entries?
double[] point = new double[6];
location = getDataPoint(location, track, point);
if (points % chartSamplingFrequency == 0) {
theData.add(point);
@@ -35,8 +35,7 @@ import android.widget.RadioGroup;
*/
public class ChartSettingsDialog extends Dialog {
private RadioButton distance;
private CheckBox elevation;
private CheckBox speed;
private CheckBox[] series;
public ChartSettingsDialog(Context context) {
super(context);
@@ -61,8 +60,18 @@ public class ChartSettingsDialog extends Dialog {
});
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
elevation = (CheckBox) findViewById(R.id.chart_settings_elevation);
speed = (CheckBox) findViewById(R.id.chart_settings_speed);
series = new CheckBox[ChartView.NUM_SERIES];
series[ChartView.ELEVATION_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_elevation);
series[ChartView.SPEED_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_speed);
series[ChartView.POWER_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_power);
series[ChartView.CADENCE_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_cadence);
series[ChartView.HEART_RATE_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
}
public void setup(ChartActivity chart) {
@@ -74,8 +83,9 @@ public class ChartSettingsDialog extends Dialog {
rd.check(chart.getMode() == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance
: R.id.chart_settings_by_time);
elevation.setChecked(chart.isSeriesEnabled(ChartView.ELEVATION_SERIES));
speed.setChecked(chart.isSeriesEnabled(ChartView.SPEED_SERIES));
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
series[i].setChecked(chart.isSeriesEnabled(i));
}
}
private void handleOk() {
@@ -83,8 +93,9 @@ public class ChartSettingsDialog extends Dialog {
chart.setMode(distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME);
// TODO: check that something is visible.
chart.setSeriesEnabled(ChartView.ELEVATION_SERIES, elevation.isChecked());
chart.setSeriesEnabled(ChartView.SPEED_SERIES, speed.isChecked());
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chart.setSeriesEnabled(i, series[i].isChecked());
}
dismiss();
}
@@ -42,6 +42,7 @@ public class ChartValueSeries {
private double min = 0;
private double max = 1;
private int effectiveMax = 0;
private int absoluteMax = Integer.MIN_VALUE;
private int effectiveMin = 0;
private double spread = 0;
private boolean enabled = true;
@@ -131,7 +132,7 @@ public class ChartValueSeries {
if (min < 0) {
effectiveMin -= rounding;
}
spread = effectiveMax - effectiveMin;
spread = getMax() - effectiveMin;
}
/**
@@ -139,7 +140,7 @@ public class ChartValueSeries {
*/
public int getMaxLabelLength() {
String minS = format.format(effectiveMin);
String maxS = format.format(effectiveMax);
String maxS = format.format(getMax());
return Math.max(minS.length(), maxS.length());
}
@@ -151,10 +152,16 @@ public class ChartValueSeries {
}
/**
* @return The rounded up maximum value
* @return The minimum of the rounded up max value and the effective max.
*/
public int getMax() {
return effectiveMax;
// Return the effective max if no absolute is set
// or the effective max is less than the absolute.
if ((absoluteMax == Integer.MIN_VALUE) || (effectiveMax < absoluteMax)) {
return effectiveMax;
} else {
return absoluteMax;
}
}
/**
@@ -211,4 +218,16 @@ public class ChartValueSeries {
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getAbsoluteMax() {
return absoluteMax;
}
public void setAbsoluteMax(int absoluteMax) {
this.absoluteMax = absoluteMax;
}
public boolean hasData() {
return monitor.hasData();
}
}
@@ -87,7 +87,10 @@ public class ChartView extends View {
*/
public static final int ELEVATION_SERIES = 0;
public static final int SPEED_SERIES = 1;
public static final int NUM_SERIES = 2;
public static final int POWER_SERIES = 2;
public static final int CADENCE_SERIES = 3;
public static final int HEART_RATE_SERIES = 4;
public static final int NUM_SERIES = 5;
private ChartValueSeries[] series;
private final ExtremityMonitor xMonitor = new ExtremityMonitor();
@@ -172,38 +175,44 @@ public class ChartView extends View {
series = new ChartValueSeries[NUM_SERIES];
// Create the paints
Paint fillPaint1 = new Paint();
fillPaint1.setStyle(Style.FILL);
fillPaint1.setColor(context.getResources().getColor(R.color.green));
fillPaint1.setAntiAlias(true);
Paint fillPaint2 = new Paint();
fillPaint2.setStyle(Style.FILL);
fillPaint2.setColor(
context.getResources().getColor(R.color.blue_transparent));
fillPaint2.setAntiAlias(true);
Paint strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(R.color.blue));
strokePaint.setAntiAlias(true);
Paint fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(R.color.green));
fillPaint.setAntiAlias(true);
// Create the value series
series[ELEVATION_SERIES] =
new ChartValueSeries(context,
"###,###",
fillPaint1,
fillPaint,
null,
100,
R.string.elevation);
series[SPEED_SERIES] =
new ChartValueSeries(context,
"###,###.0",
fillPaint2,
strokePaint,
5,
R.string.speed);
series[SPEED_SERIES] = getSeries(context, R.color.blue_transparent,
R.color.blue, "###,###.0", R.string.speed);
series[POWER_SERIES] = getSeries(context, R.color.red_transparent,
R.color.red, "###,###", R.string.power);
series[POWER_SERIES].setAbsoluteMax(1500);
series[CADENCE_SERIES] = getSeries(context, R.color.purple_transparent,
R.color.purple, "###,###", R.string.cadence);
series[HEART_RATE_SERIES] = getSeries(context, R.color.yellow_transparent,
R.color.yellow, "###,###", R.string.heart_rate);
}
private ChartValueSeries getSeries(Context context,
int fillColor, int strokeColor, String format, int seriesName) {
Paint fillPaint = new Paint();
fillPaint.setStyle(Style.FILL);
fillPaint.setColor(context.getResources().getColor(fillColor));
fillPaint.setAntiAlias(true);
Paint strokePaint = new Paint();
strokePaint.setStyle(Style.STROKE);
strokePaint.setColor(context.getResources().getColor(strokeColor));
strokePaint.setAntiAlias(true);
return new ChartValueSeries(context, format, fillPaint, strokePaint, 5,
seriesName);
}
public void clearWaypoints() {
@@ -266,13 +275,25 @@ public class ChartView extends View {
*/
public synchronized void addDataPoint(double[] theData) {
data.add(theData);
addDataPointInternal(theData);
updateDimensions();
setupPath();
}
private void addDataPointInternal(double[] theData) {
xMonitor.update(theData[0]);
int min = Math.min(series.length, theData.length - 1);
for (int i = 0; i < min; i++) {
series[i].update(theData[i + 1]);
if (!Double.isNaN(theData[i + 1])) {
series[i].update(theData[i + 1]);
}
}
// Fill in the extra's if needed.
for (int i = theData.length; i < series.length; i++) {
if (series[i].hasData()) {
series[i].update(0);
}
}
updateDimensions();
setupPath();
}
/**
@@ -284,11 +305,7 @@ public class ChartView extends View {
data.addAll(theData);
for (int i = 0; i < theData.size(); i++) {
double d[] = theData.get(i);
xMonitor.update(d[0]);
int min = Math.min(series.length, d.length - 1);
for (int j = 0; j < min; j++) {
series[j].update(d[j + 1]);
}
addDataPointInternal(d);
}
updateDimensions();
setupPath();
@@ -526,7 +543,7 @@ public class ChartView extends View {
// Draw the data series
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled()) {
if (cvs.isEnabled() && cvs.hasData()) {
cvs.drawPath(c);
}
}
@@ -560,7 +577,7 @@ public class ChartView extends View {
c.translate(getScrollX(), 0);
drawYAxis(c);
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled()) {
if (cvs.isEnabled() && cvs.hasData()) {
drawYLabels(cvs, c);
}
}
@@ -576,13 +593,13 @@ public class ChartView extends View {
private void drawSeriesTitles(Canvas c) {
int sections = 1;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled()) {
if (cvs.isEnabled() && cvs.hasData()) {
sections++;
}
}
int j = 0;
for (ChartValueSeries cvs : series) {
if (cvs.isEnabled()) {
if (cvs.isEnabled() && cvs.hasData()) {
int y = (int) (w * zoomLevel * ((double) (++j) / sections));
c.drawText(cvs.getTitle(), y, TOP_BORDER, cvs.getPaint());
}
@@ -601,11 +618,11 @@ public class ChartView extends View {
return;
}
int min = Math.min(series.length, data.get(0).length - 1);
// All of the data points to the respective series.
for (int i = 0; i < data.size(); i++) {
double[] d = data.get(i);
int min = Math.min(series.length, d.length - 1);
for (int j = 0; j < min; j++) {
ChartValueSeries cvs = series[j];
Path path = cvs.getPath();
@@ -616,17 +633,36 @@ public class ChartView extends View {
// Close the path.
int yCorner = TOP_BORDER + effectiveHeight;
int xCorner = getX(data.get(0)[0]);
int min = series.length;
for (int j = 0; j < min; j++) {
ChartValueSeries cvs = series[j];
Path path = cvs.getPath();
// Bottom right corner
path.lineTo(getX(data.get(data.size() - 1)[0]), yCorner);
// Bottom left corner
path.lineTo(xCorner, yCorner);
// Top right corner
path.lineTo(xCorner, getY(cvs, data.get(0)[j + 1]));
int first = getFirstPointPopulatedIndex(j + 1);
if (first != -1) {
// Bottom right corner
path.lineTo(getX(data.get(data.size() - 1)[0]), yCorner);
// Bottom left corner
path.lineTo(xCorner, yCorner);
// Top right corner
path.lineTo(xCorner, getY(cvs, data.get(first)[j + 1]));
}
}
}
/**
* Find the index of the first point which has a series populated.
* @param seriesIndex The index of the value series to search for.
* @return The index in the first data for the point in the series that has series
* index value populated or -1 if none is found.
*/
private int getFirstPointPopulatedIndex(int seriesIndex) {
for (int i = 0; i < data.size(); i++) {
if (data.get(i).length > seriesIndex) {
return i;
}
}
return -1;
}
/**
* Update the histogram dimensions.
@@ -17,6 +17,7 @@ package com.google.android.apps.mytracks;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
@@ -63,8 +64,8 @@ class MenuManager {
return true;
}
case R.id.menu_list_tracks: {
Intent startIntent = new Intent(activity, MyTracksList.class);
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_TRACK);
activity.startActivityForResult(new Intent(activity, MyTracksList.class),
MyTracksConstants.SHOW_TRACK);
return true;
}
case R.id.menu_list_markers: {
@@ -73,20 +74,17 @@ class MenuManager {
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
return true;
}
case R.id.menu_sensor_state: {
return startActivity(SensorStateActivity.class);
}
case R.id.menu_settings: {
Intent startIntent = new Intent(activity, MyTracksSettings.class);
activity.startActivity(startIntent);
return true;
return startActivity(MyTracksSettings.class);
}
case R.id.menu_aggregated_stats: {
Intent startIntent = new Intent(activity, AggregatedStatsActivity.class);
activity.startActivity(startIntent);
return true;
return startActivity(AggregatedStatsActivity.class);
}
case R.id.menu_help: {
Intent startIntent = new Intent(activity, WelcomeActivity.class);
activity.startActivity(startIntent);
return true;
return startActivity(WelcomeActivity.class);
}
case MyTracksConstants.MENU_CLEAR_MAP: {
activity.setSelectedTrackId(-1);
@@ -95,4 +93,9 @@ class MenuManager {
}
return false;
}
private boolean startActivity(Class<? extends Activity> activityClass) {
activity.startActivity(new Intent(activity, activityClass));
return true;
}
}
@@ -63,7 +63,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -229,7 +228,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
}
try {
return trackRecordingService.isRecording();
} catch (RemoteException e) {
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Log.e(MyTracksConstants.TAG, "MyTracks: Remote exception.", e);
// Fall back to alternative check method.
@@ -411,6 +412,11 @@ public class MyTracks extends TabActivity implements OnTouchListener,
return dialogManager.onCreateDialog(id, args);
}
@Override
protected Dialog onCreateDialog(int id) {
return dialogManager.onCreateDialog(id, null);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
@@ -914,7 +920,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker?");
return -4;
}
} catch (RemoteException e) {
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker.", e);
@@ -954,7 +962,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker?");
return -4;
}
} catch (RemoteException e) {
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(MyTracksConstants.TAG, "Cannot insert statistics marker?", e);
@@ -1022,7 +1032,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
setSelectedTrackId(recordingTrackId);
Toast.makeText(this, getString(R.string.status_now_recording),
Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Toast.makeText(this,
getString(R.string.error_unable_to_start_recording),
Toast.LENGTH_SHORT).show();
@@ -1055,7 +1067,9 @@ public class MyTracks extends TabActivity implements OnTouchListener,
long currentTrackId = recordingTrackId;
try {
trackRecordingService.endCurrentTrack();
} catch (RemoteException e) {
// TODO: We catch Exception, because after eliminating the service process
// all exceptions it may throw are no longer wrapped in a RemoteException.
} catch (Exception e) {
Log.e(MyTracksConstants.TAG, "Unable to stop recording.", e);
}
Intent intent = new Intent(MyTracks.this, MyTracksDetails.class);
@@ -1120,7 +1134,7 @@ public class MyTracks extends TabActivity implements OnTouchListener,
long getSelectedTrackId() {
return selectedTrackId;
}
/**
* Binds to track recording service if it is running.
*/
@@ -15,12 +15,17 @@
*/
package com.google.android.apps.mytracks;
import java.util.ArrayList;
import java.util.List;
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.BluetoothDeviceUtils;
import com.google.android.maps.mytracks.R;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
@@ -30,6 +35,7 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.Settings;
/**
* An activity that let's the user see and edit the settings.
@@ -53,6 +59,7 @@ public class MyTracksSettings extends PreferenceActivity {
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
private BackupPreferencesListener backupListener;
private SharedPreferences preferences;
/** Called when the activity is first created. */
@@ -96,6 +103,19 @@ public class MyTracksSettings extends PreferenceActivity {
});
updatePreferenceUnits(metricUnitsPreference.isChecked());
ListPreference sensorTypePreference =
(ListPreference) findPreference(getString(R.string.sensor_type_key));
sensorTypePreference.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference,
Object newValue) {
updateSensorSettings((String) newValue);
return true;
}
});
updateSensorSettings(sensorTypePreference.getValue());
// Disable TTS announcement preference if not available
if (!apiFeatures.hasTextToSpeech()) {
IntegerListPreference announcementFrequency =
@@ -112,6 +132,7 @@ public class MyTracksSettings extends PreferenceActivity {
protected void onResume() {
super.onResume();
configureBluetoothPreferences();
Preference backupNowPreference =
findPreference(getString(R.string.backup_to_sd_key));
Preference restoreNowPreference =
@@ -158,7 +179,15 @@ public class MyTracksSettings extends PreferenceActivity {
getPreferenceManager().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(backupListener);
super.onDestroy();
super.onPause();
}
private void updateSensorSettings(String sensorType) {
boolean usesBluetooth =
getString(R.string.zephyr_sensor_type).equals(sensorType);
findPreference(
getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth);
findPreference(
getString(R.string.bluetooth_pairing_key)).setEnabled(usesBluetooth);
}
/**
@@ -194,4 +223,44 @@ public class MyTracksSettings extends PreferenceActivity {
? R.array.split_frequency_options
: R.array.split_frequency_options_ft);
}
/**
* Configures preference actions related to bluetooth.
*/
private void configureBluetoothPreferences() {
if (BluetoothDeviceUtils.isBluetoothMethodSupported()) {
// Populate the list of bluetooth devices
populateBluetoothDeviceList();
// Make the pair devices preference go to the system preferences
findPreference(getString(R.string.bluetooth_pairing_key))
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent settingsIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
startActivity(settingsIntent);
return false;
}
});
}
}
/**
* Populates the list preference with all available bluetooth devices.
*/
private void populateBluetoothDeviceList() {
// Build the list of entries and their values
List<String> entries = new ArrayList<String>();
List<String> entryValues = new ArrayList<String>();
// The actual devices
BluetoothDeviceUtils.getInstance().populateDeviceLists(entries, entryValues);
CharSequence[] entriesArray = entries.toArray(new CharSequence[entries.size()]);
CharSequence[] entryValuesArray = entryValues.toArray(new CharSequence[entryValues.size()]);
ListPreference devicesPreference =
(ListPreference) findPreference(getString(R.string.bluetooth_sensor_key));
devicesPreference.setEntryValues(entryValuesArray);
devicesPreference.setEntries(entriesArray);
}
}
@@ -0,0 +1,193 @@
/*
* 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.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.MyTracksConstants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* An activity that displays information about sensors.
*
* @author Sandor Dornbush
*/
public class SensorStateActivity extends Activity {
private static final SimpleDateFormat TIMESTAMP_FORMAT =
new SimpleDateFormat("HH:mm:ss");
private static final long REFRESH_PERIOD_MS = 250;
private final StatsUtilities utils;
/**
* This timer periodically invokes the refresh timer task.
*/
private Timer timer;
private final Runnable stateUpdater = new Runnable() {
public void run() {
updateState();
}
};
/**
* A task which will update the U/I.
*/
private class RefreshTask extends TimerTask {
@Override
public void run() {
runOnUiThread(stateUpdater);
}
};
public SensorStateActivity() {
utils = new StatsUtilities(this);
Log.w(TAG, "SensorStateActivity()");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "SensorStateActivity.onCreate");
setContentView(R.layout.sensor_state);
updateState();
}
@Override
protected void onResume() {
super.onResume();
timer = new Timer();
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
}
@Override
protected void onPause() {
super.onPause();
timer.cancel();
timer.purge();
timer = null;
}
protected void updateState() {
MyTracks mt = MyTracks.getInstance();
ITrackRecordingService service =
mt == null ? null : mt.getTrackRecordingService();
if (service == null) {
Log.d(MyTracksConstants.TAG, "Could not get track recording service.");
updateSensorState(Sensor.SensorState.NONE);
updateSensorData(null);
return;
}
Sensor.SensorDataSet sds = null;
try {
byte[] buff = service.getSensorData();
if (buff != null) {
sds = Sensor.SensorDataSet.parseFrom(buff);
updateSensorData(sds);
}
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "Could not read sensor data.", e);
} catch (InvalidProtocolBufferException e) {
Log.e(MyTracksConstants.TAG, "Could not read sensor data.", e);
}
updateSensorData(sds);
try {
int i = service.getSensorState();
updateSensorState(Sensor.SensorState.valueOf(i));
} catch (RemoteException e) {
Log.e(MyTracksConstants.TAG, "Could not read sensor state.", e);
updateSensorState(Sensor.SensorState.NONE);
}
}
private void updateSensorState(Sensor.SensorState state) {
TextView sensorTime =
((TextView) findViewById(R.id.sensor_state_register));
sensorTime.setText(SensorUtils.getStateAsString(state, this));
}
protected void updateSensorData(Sensor.SensorDataSet sds) {
if (sds == null) {
utils.setUnknown(R.id.sensor_data_time_register);
utils.setUnknown(R.id.cadence_state_register);
utils.setUnknown(R.id.power_state_register);
utils.setUnknown(R.id.heart_rate_register);
return;
}
TextView sensorTime =
((TextView) findViewById(R.id.sensor_data_time_register));
sensorTime.setText(
TIMESTAMP_FORMAT.format(new Date(sds.getCreationTime())));
if (sds.hasPower() && sds.getPower().hasValue()
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.power_state_register,
Integer.toString(sds.getPower().getValue()));
} else {
utils.setText(R.id.power_state_register,
SensorUtils.getStateAsString(
sds.hasPower()
? sds.getPower().getState()
: Sensor.SensorState.NONE,
this));
}
if (sds.hasCadence() && sds.getCadence().hasValue()
&& sds.getCadence().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.cadence_state_register,
Integer.toString(sds.getCadence().getValue()));
} else {
utils.setText(R.id.cadence_state_register,
SensorUtils.getStateAsString(
sds.hasCadence()
? sds.getCadence().getState()
: Sensor.SensorState.NONE,
this));
}
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.heart_rate_register,
Integer.toString(sds.getHeartRate().getValue()));
} else {
utils.setText(R.id.heart_rate_register,
SensorUtils.getStateAsString(
sds.hasHeartRate()
? sds.getHeartRate().getState()
: Sensor.SensorState.NONE,
this));
}
}
}
@@ -1,41 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.content;
import java.util.Vector;
/**
* An interface for an object that can generate descriptions of waypoints.
*
* @author Sandor Dornbush
*/
public interface DescriptionGenerator {
/**
* Generate a description of the waypoint.
*/
public String generateWaypointDescription(Waypoint waypoint);
/**
* Generates a description for a track (with information about the
* statistics).
*
* @param track the track
* @return a track description
*/
public String generateTrackDescription(Track track, Vector<Double> distances,
Vector<Double> elevations);
}
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.content;
import com.google.android.apps.mytracks.MyTracksConstants;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -38,7 +40,7 @@ import android.util.Log;
public class MyTracksProvider extends ContentProvider {
private static final String DATABASE_NAME = "mytracks.db";
private static final int DATABASE_VERSION = 17;
private static final int DATABASE_VERSION = 18;
private static final int TRACKPOINTS = 1;
private static final int TRACKPOINTS_ID = 2;
private static final int TRACKS = 3;
@@ -70,7 +72,8 @@ public class MyTracksProvider extends ContentProvider {
+ TrackPointsColumns.ALTITUDE + " FLOAT, "
+ TrackPointsColumns.ACCURACY + " FLOAT, "
+ TrackPointsColumns.SPEED + " FLOAT, "
+ TrackPointsColumns.BEARING + " FLOAT);");
+ TrackPointsColumns.BEARING + " FLOAT, "
+ TrackPointsColumns.SENSOR + " BLOB);");
db.execSQL("CREATE TABLE " + TRACKS_TABLE + " ("
+ TracksColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ TracksColumns.NAME + " STRING, "
@@ -132,12 +135,19 @@ public class MyTracksProvider extends ContentProvider {
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TRACKPOINTS_TABLE);
db.execSQL("DROP TABLE IF EXISTS " + TRACKS_TABLE);
db.execSQL("DROP TABLE IF EXISTS " + WAYPOINTS_TABLE);
onCreate(db);
if (oldVersion < 17) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TRACKPOINTS_TABLE);
db.execSQL("DROP TABLE IF EXISTS " + TRACKS_TABLE);
db.execSQL("DROP TABLE IF EXISTS " + WAYPOINTS_TABLE);
onCreate(db);
} else if (oldVersion >= 17) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", adding sensor column.");
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
}
}
}
@@ -235,6 +245,7 @@ public class MyTracksProvider extends ContentProvider {
}
}
@Override
public int bulkInsert(Uri url, ContentValues[] valuesBulk) {
Log.d(MyTracksProvider.TAG, "MyTracksProvider.bulkInsert");
@@ -346,6 +357,8 @@ public class MyTracksProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URL " + url);
}
Log.i(MyTracksConstants.TAG,
"Build query: " + qb.buildQuery(projection, selection, selectionArgs, null, null, sortOrder, null));
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
sortOrder);
c.setNotificationUri(getContext().getContentResolver(), url);
@@ -1,351 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.content;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import java.util.List;
/**
* Utility to access data from the mytracks content provider.
*
* @author Rodrigo Damazio
*/
public interface MyTracksProviderUtils {
/**
* Authority (first part of URI) for the MyTracks content provider:
*/
public static final String AUTHORITY = "com.google.android.maps.mytracks";
/**
* Deletes all tracks (including track points) from the provider.
*/
void deleteAllTracks();
/**
* Deletes a track with the given track id.
*
* @param trackId the unique track id
*/
void deleteTrack(long trackId);
/**
* Deletes a way point with the given way point id.
* This will also correct the next statistics way point after the deleted one
* to reflect the deletion.
* The generator is needed to stitch together statistics waypoints.
*
* @param waypointId the unique way point id
* @param descriptionGenerator the class to generate descriptions
*/
void deleteWaypoint(long waypointId,
DescriptionGenerator descriptionGenerator);
/**
* Finds the next statistics waypoint after the given waypoint.
*
* @param waypoint a given waypoint
* @return the next statistics waypoint, or null if none found.
*/
Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint);
/**
* Updates the waypoint in the provider.
*
* @param waypoint
* @return true if successful
*/
boolean updateWaypoint(Waypoint waypoint);
/**
* Finds the last recorded location from the location provider.
*
* @return the last location, or null if no locations available
*/
Location getLastLocation();
/**
* Finds the first recorded waypoint for a given track from the location
* provider.
* This is a special waypoint that holds the stats for current segment.
*
* @param trackId the id of the track the waypoint belongs to
* @return the first waypoint, or null if no waypoints available
*/
Waypoint getFirstWaypoint(long trackId);
/**
* Finds the given waypoint from the location provider.
*
* @param waypointId
* @return the waypoint, or null if it does not exist
*/
Waypoint getWaypoint(long waypointId);
/**
* Finds the last recorded location id from the track points provider.
*
* @param trackId find last location on this track
* @return the location id, or -1 if no locations available
*/
long getLastLocationId(long trackId);
/**
* Finds the id of the 1st waypoint for a given track.
* The 1st waypoint is special as it contains the stats for the current
* segment.
*
* @param trackId find last location on this track
* @return the waypoint id, or -1 if no waypoints are available
*/
long getFirstWaypointId(long trackId);
/**
* Finds the id of the 1st waypoint for a given track.
* The 1st waypoint is special as it contains the stats for the current
* segment.
*
* @param trackId find last location on this track
* @return the waypoint id, or -1 if no waypoints are available
*/
long getLastWaypointId(long trackId);
/**
* Finds the last recorded track from the track provider.
*
* @return the last track, or null if no tracks available
*/
Track getLastTrack();
/**
* Finds the last recorded track id from the tracks provider.
*
* @return the track id, or -1 if no tracks available
*/
long getLastTrackId();
/**
* Finds a location by given unique id.
*
* @param id the desired id
* @return a Location object, or null if not found
*/
Location getLocation(long id);
/**
* Creates a cursor over the locations in the track points provider which
* iterates over a given range of unique ids.
* Caller gets to own the returned cursor. Don't forget to close it.
*
* @param trackId the id of the track for which to get the points
* @param minTrackPointId the minimum id for the track points
* @param maxLocations maximum number of locations retrieved
* @param descending if true the results will be returned in descending id
* order (latest location first)
* @return A cursor over the selected range of locations
*/
Cursor getLocationsCursor(long trackId, long minTrackPointId,
int maxLocations, boolean descending);
/**
* Creates a cursor over the waypoints of a track.
* Caller gets to own the returned cursor. Don't forget to close it.
*
* @param trackId the id of the track for which to get the points
* @param minWaypointId the minimum id for the track points
* @param maxWaypoints the maximum number of waypoints to return
* @return A cursor over the selected range of locations
*/
Cursor getWaypointsCursor(long trackId, long minWaypointId,
int maxWaypoints);
/**
* Finds a track by given unique track id.
* Note that the returned track object does not have any track points
* attached. Use {@link #getTrackPoints(Track, int)} to load the track points.
*
* @param id desired unique track id
* @return a Track object, or null if not found
*/
Track getTrack(long id);
/**
* Retrieves all tracks without track points. If no tracks exist an empty
* list will be returned. Use {@link #getTrackPoints(Track, int)} to load
* the track points.
*
* @return a list of all the recorded tracks
*/
List<Track> getAllTracks();
/**
* Loads the track points for a given track.
*
* @param track assumes that startId and stopId are filled in correctly
* @param maxPoints maximum number of points to load, or -1 for no limit
* (oldest ones will be discarded)
* @return the id of the last location in the track
*/
long getTrackPoints(Track track, int maxPoints);
/**
* Creates a cursor over the tracks provider with a given selection.
* Caller gets to own the returned cursor. Don't forget to close it.
*
* @param selection a given selection
* @return a cursor of the selected tracks
*/
Cursor getTracksCursor(String selection);
/**
* Inserts a track in the tracks provider.
* Note: This does not insert any track points.
* Use {@link #insertTrackPoint(Location, long)} to insert them.
*
* @param track the track to insert
* @return the content provider URI for the inserted track
*/
Uri insertTrack(Track track);
/**
* Inserts a track point in the tracks provider.
*
* @param location the location to insert
* @return the content provider URI for the inserted track point
*/
Uri insertTrackPoint(Location location, long trackId);
/**
* Inserts multiple track points in a single operation.
*
* @param locations an array of locations to insert
* @param length the number of locations (from the beginning of the array)
* to actually insert, or -1 for all of them
* @param trackId the ID of the track to insert the points into
* @return the number of points inserted
*/
int bulkInsertTrackPoints(Location[] locations, int length, long trackId);
/**
* Inserts a waypoint in the provider.
*
* @param waypoint the waypoint to insert
* @return the content provider URI for the inserted track
*/
Uri insertWaypoint(Waypoint waypoint);
/**
* Tests if a track with given id exists.
*
* @param id the unique id
* @return true if track exists
*/
boolean trackExists(long id);
/**
* Updates a track in the content provider.
* Note: This will not update any track points.
*
* @param track a given track
*/
void updateTrack(Track track);
/**
* Creates a Track object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with tracks
* @return a new Track object
*/
Track createTrack(Cursor cursor);
/**
* Creates the ContentValues for a given Track object.
*
* Note: If the track has an id<0 the id column will not be filled.
*
* @param track a given track object
* @return a filled in ContentValues object
*/
ContentValues createContentValues(Track track);
/**
* Creates a location object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with locations
* @return a new location object
*/
Location createLocation(Cursor cursor);
/**
* Fill a location object with values from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with locations
* @param location a location object to be overwritten
*/
void fillLocation(Cursor cursor, Location location);
/**
* Creates a waypoint object from a given cursor.
*
* @param cursor a cursor pointing at a db or provider with waypoints.
* @return a new waypoint object
*/
Waypoint createWaypoint(Cursor cursor);
/**
* A factory which can produce instances of {@link MyTracksProviderUtils},
* and can be overridden in tests (a.k.a. poor man's guice).
*/
public static class Factory {
private static Factory instance = new Factory();
/**
* Creates and returns an instance of {@link MyTracksProviderUtils} which
* uses the given context to access its data.
*/
public static MyTracksProviderUtils get(Context context) {
return instance.newForContext(context);
}
/**
* Returns the global instance of this factory.
*/
public static Factory getInstance() {
return instance;
}
/**
* Overrides the global instance for this factory, to be used for testing.
* If used, don't forget to set it back to the original value after the
* test is run.
*/
public static void overrideInstance(Factory factory) {
instance = factory;
}
/**
* Creates an instance of {@link MyTracksProviderUtils}.
*/
protected MyTracksProviderUtils newForContext(Context context) {
return new MyTracksProviderUtilsImpl(context.getContentResolver());
}
}
}
@@ -1,914 +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.content;
import com.google.android.apps.mytracks.stats.TripStatistics;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class providing easy access to locations and tracks in the
* MyTracksProvider. All static members.
*
* @author Leif Hendrik Wilden
*/
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
private final ContentResolver contentResolver;
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
this.contentResolver = contentResolver;
}
/**
* Creates the ContentValues for a given location object.
*
* @param location a given location
* @param trackId the id of the track it belongs to
* @return a filled in ContentValues object
*/
private static ContentValues createContentValues(
Location location, long trackId) {
ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId);
values.put(TrackPointsColumns.LATITUDE,
(int) (location.getLatitude() * 1E6));
values.put(TrackPointsColumns.LONGITUDE,
(int) (location.getLongitude() * 1E6));
// This is an ugly hack for Samsung phones that don't properly populate the
// time field.
values.put(TrackPointsColumns.TIME,
(location.getTime() == 0)
? System.currentTimeMillis()
: location.getTime());
if (location.hasAltitude()) {
values.put(TrackPointsColumns.ALTITUDE, location.getAltitude());
}
if (location.hasBearing()) {
values.put(TrackPointsColumns.BEARING, location.getBearing());
}
if (location.hasAccuracy()) {
values.put(TrackPointsColumns.ACCURACY, location.getAccuracy());
}
if (location.hasSpeed()) {
values.put(TrackPointsColumns.SPEED, location.getSpeed());
}
return values;
}
@Override
public ContentValues createContentValues(Track track) {
ContentValues values = new ContentValues();
TripStatistics stats = track.getStatistics();
// Values id < 0 indicate no id is available:
if (track.getId() >= 0) {
values.put(TracksColumns._ID, track.getId());
}
values.put(TracksColumns.NAME, track.getName());
values.put(TracksColumns.DESCRIPTION, track.getDescription());
values.put(TracksColumns.MAPID, track.getMapId());
values.put(TracksColumns.CATEGORY, track.getCategory());
values.put(TracksColumns.NUMPOINTS, track.getNumberOfPoints());
values.put(TracksColumns.STARTID, track.getStartId());
values.put(TracksColumns.STARTTIME, stats.getStartTime());
values.put(TracksColumns.STOPTIME, stats.getStopTime());
values.put(TracksColumns.STOPID, track.getStopId());
values.put(TracksColumns.TOTALDISTANCE, stats.getTotalDistance());
values.put(TracksColumns.TOTALTIME, stats.getTotalTime());
values.put(TracksColumns.MOVINGTIME, stats.getMovingTime());
values.put(TracksColumns.MAXLAT, stats.getTop());
values.put(TracksColumns.MINLAT, stats.getBottom());
values.put(TracksColumns.MAXLON, stats.getRight());
values.put(TracksColumns.MINLON, stats.getLeft());
values.put(TracksColumns.AVGSPEED, stats.getAverageSpeed());
values.put(TracksColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
values.put(TracksColumns.MAXSPEED, stats.getMaxSpeed());
values.put(TracksColumns.MINELEVATION, stats.getMinElevation());
values.put(TracksColumns.MAXELEVATION, stats.getMaxElevation());
values.put(TracksColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
values.put(TracksColumns.MINGRADE, stats.getMinGrade());
values.put(TracksColumns.MAXGRADE, stats.getMaxGrade());
return values;
}
private static ContentValues createContentValues(Waypoint waypoint) {
ContentValues values = new ContentValues();
TripStatistics stats = waypoint.getStatistics();
// Values id < 0 indicate no id is available:
if (waypoint.getId() >= 0) {
values.put(WaypointsColumns._ID, waypoint.getId());
}
values.put(WaypointsColumns.NAME, waypoint.getName());
values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription());
values.put(WaypointsColumns.CATEGORY, waypoint.getCategory());
values.put(WaypointsColumns.ICON, waypoint.getIcon());
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId());
values.put(WaypointsColumns.TYPE, waypoint.getType());
values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration());
values.put(WaypointsColumns.STARTTIME, stats.getStartTime());
values.put(WaypointsColumns.STARTID, waypoint.getStartId());
values.put(WaypointsColumns.STOPID, waypoint.getStopId());
values.put(WaypointsColumns.TOTALDISTANCE, stats.getTotalDistance());
values.put(WaypointsColumns.TOTALTIME, stats.getTotalTime());
values.put(WaypointsColumns.MOVINGTIME, stats.getMovingTime());
values.put(WaypointsColumns.AVGSPEED, stats.getAverageSpeed());
values.put(WaypointsColumns.AVGMOVINGSPEED, stats.getAverageMovingSpeed());
values.put(WaypointsColumns.MAXSPEED, stats.getMaxSpeed());
values.put(WaypointsColumns.MINELEVATION, stats.getMinElevation());
values.put(WaypointsColumns.MAXELEVATION, stats.getMaxElevation());
values.put(WaypointsColumns.ELEVATIONGAIN, stats.getTotalElevationGain());
values.put(WaypointsColumns.MINGRADE, stats.getMinGrade());
values.put(WaypointsColumns.MAXGRADE, stats.getMaxGrade());
Location location = waypoint.getLocation();
if (location != null) {
values.put(WaypointsColumns.LATITUDE,
(int) (location.getLatitude() * 1E6));
values.put(WaypointsColumns.LONGITUDE,
(int) (location.getLongitude() * 1E6));
values.put(WaypointsColumns.TIME, location.getTime());
if (location.hasAltitude()) {
values.put(WaypointsColumns.ALTITUDE, location.getAltitude());
}
if (location.hasBearing()) {
values.put(WaypointsColumns.BEARING, location.getBearing());
}
if (location.hasAccuracy()) {
values.put(WaypointsColumns.ACCURACY, location.getAccuracy());
}
if (location.hasSpeed()) {
values.put(WaypointsColumns.SPEED, location.getSpeed());
}
}
return values;
}
@Override
public Location createLocation(Cursor cursor) {
Location location = new Location("");
fillLocation(cursor, location);
return location;
}
@Override
public void fillLocation(Cursor cursor, Location location) {
location.reset();
int idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
int idxLongitude =
cursor.getColumnIndexOrThrow(TrackPointsColumns.LONGITUDE);
int idxAltitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.ALTITUDE);
int idxTime = cursor.getColumnIndexOrThrow(TrackPointsColumns.TIME);
int idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
if (!cursor.isNull(idxLatitude)) {
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
}
if (!cursor.isNull(idxLongitude)) {
location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6);
}
if (!cursor.isNull(idxAltitude)) {
location.setAltitude(cursor.getFloat(idxAltitude));
}
if (!cursor.isNull(idxTime)) {
location.setTime(cursor.getLong(idxTime));
}
if (!cursor.isNull(idxBearing)) {
location.setBearing(cursor.getFloat(idxBearing));
}
if (!cursor.isNull(idxSpeed)) {
location.setSpeed(cursor.getFloat(idxSpeed));
}
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
}
}
@Override
public Track createTrack(Cursor cursor) {
int idxId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
int idxName = cursor.getColumnIndexOrThrow(TracksColumns.NAME);
int idxDescription =
cursor.getColumnIndexOrThrow(TracksColumns.DESCRIPTION);
int idxMapId = cursor.getColumnIndexOrThrow(TracksColumns.MAPID);
int idxCategory = cursor.getColumnIndexOrThrow(TracksColumns.CATEGORY);
int idxStartId = cursor.getColumnIndexOrThrow(TracksColumns.STARTID);
int idxStartTime = cursor.getColumnIndexOrThrow(TracksColumns.STARTTIME);
int idxStopTime = cursor.getColumnIndexOrThrow(TracksColumns.STOPTIME);
int idxStopId = cursor.getColumnIndexOrThrow(TracksColumns.STOPID);
int idxNumPoints = cursor.getColumnIndexOrThrow(TracksColumns.NUMPOINTS);
int idxMaxlat = cursor.getColumnIndexOrThrow(TracksColumns.MAXLAT);
int idxMinlat = cursor.getColumnIndexOrThrow(TracksColumns.MINLAT);
int idxMaxlon = cursor.getColumnIndexOrThrow(TracksColumns.MAXLON);
int idxMinlon = cursor.getColumnIndexOrThrow(TracksColumns.MINLON);
int idxTotalDistance =
cursor.getColumnIndexOrThrow(TracksColumns.TOTALDISTANCE);
int idxTotalTime = cursor.getColumnIndexOrThrow(TracksColumns.TOTALTIME);
int idxMovingTime = cursor.getColumnIndexOrThrow(TracksColumns.MOVINGTIME);
int idxMaxSpeed = cursor.getColumnIndexOrThrow(TracksColumns.MAXSPEED);
int idxMinElevation =
cursor.getColumnIndexOrThrow(TracksColumns.MINELEVATION);
int idxMaxElevation =
cursor.getColumnIndexOrThrow(TracksColumns.MAXELEVATION);
int idxElevationGain =
cursor.getColumnIndexOrThrow(TracksColumns.ELEVATIONGAIN);
int idxMinGrade = cursor.getColumnIndexOrThrow(TracksColumns.MINGRADE);
int idxMaxGrade = cursor.getColumnIndexOrThrow(TracksColumns.MAXGRADE);
Track track = new Track();
TripStatistics stats = track.getStatistics();
if (!cursor.isNull(idxId)) {
track.setId(cursor.getLong(idxId));
}
if (!cursor.isNull(idxName)) {
track.setName(cursor.getString(idxName));
}
if (!cursor.isNull(idxDescription)) {
track.setDescription(cursor.getString(idxDescription));
}
if (!cursor.isNull(idxMapId)) {
track.setMapId(cursor.getString(idxMapId));
}
if (!cursor.isNull(idxCategory)) {
track.setCategory(cursor.getString(idxCategory));
}
if (!cursor.isNull(idxStartId)) {
track.setStartId(cursor.getInt(idxStartId));
}
if (!cursor.isNull(idxStartTime)) {
stats.setStartTime(cursor.getLong(idxStartTime));
}
if (!cursor.isNull(idxStopTime)) {
stats.setStopTime(cursor.getLong(idxStopTime));
}
if (!cursor.isNull(idxStopId)) {
track.setStopId(cursor.getInt(idxStopId));
}
if (!cursor.isNull(idxNumPoints)) {
track.setNumberOfPoints(cursor.getInt(idxNumPoints));
}
if (!cursor.isNull(idxTotalDistance)) {
stats.setTotalDistance(cursor.getFloat(idxTotalDistance));
}
if (!cursor.isNull(idxTotalTime)) {
stats.setTotalTime(cursor.getLong(idxTotalTime));
}
if (!cursor.isNull(idxMovingTime)) {
stats.setMovingTime(cursor.getLong(idxMovingTime));
}
if (!cursor.isNull(idxMaxlat)
&& !cursor.isNull(idxMinlat)
&& !cursor.isNull(idxMaxlon)
&& !cursor.isNull(idxMinlon)) {
int top = cursor.getInt(idxMaxlat);
int bottom = cursor.getInt(idxMinlat);
int right = cursor.getInt(idxMaxlon);
int left = cursor.getInt(idxMinlon);
stats.setBounds(left, top, right, bottom);
}
if (!cursor.isNull(idxMaxSpeed)) {
stats.setMaxSpeed(cursor.getFloat(idxMaxSpeed));
}
if (!cursor.isNull(idxMinElevation)) {
stats.setMinElevation(cursor.getFloat(idxMinElevation));
}
if (!cursor.isNull(idxMaxElevation)) {
stats.setMaxElevation(cursor.getFloat(idxMaxElevation));
}
if (!cursor.isNull(idxElevationGain)) {
stats.setTotalElevationGain(cursor.getFloat(idxElevationGain));
}
if (!cursor.isNull(idxMinGrade)) {
stats.setMinGrade(cursor.getFloat(idxMinGrade));
}
if (!cursor.isNull(idxMaxGrade)) {
stats.setMaxGrade(cursor.getFloat(idxMaxGrade));
}
return track;
}
@Override
public Waypoint createWaypoint(Cursor cursor) {
int idxId = cursor.getColumnIndexOrThrow(WaypointsColumns._ID);
int idxName = cursor.getColumnIndexOrThrow(WaypointsColumns.NAME);
int idxDescription =
cursor.getColumnIndexOrThrow(WaypointsColumns.DESCRIPTION);
int idxCategory = cursor.getColumnIndexOrThrow(WaypointsColumns.CATEGORY);
int idxIcon = cursor.getColumnIndexOrThrow(WaypointsColumns.ICON);
int idxTrackId = cursor.getColumnIndexOrThrow(WaypointsColumns.TRACKID);
int idxType = cursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
int idxLength = cursor.getColumnIndexOrThrow(WaypointsColumns.LENGTH);
int idxDuration = cursor.getColumnIndexOrThrow(WaypointsColumns.DURATION);
int idxStartTime = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTTIME);
int idxStartId = cursor.getColumnIndexOrThrow(WaypointsColumns.STARTID);
int idxStopId = cursor.getColumnIndexOrThrow(WaypointsColumns.STOPID);
int idxTotalDistance =
cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALDISTANCE);
int idxTotalTime = cursor.getColumnIndexOrThrow(WaypointsColumns.TOTALTIME);
int idxMovingTime =
cursor.getColumnIndexOrThrow(WaypointsColumns.MOVINGTIME);
int idxMaxSpeed = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXSPEED);
int idxMinElevation =
cursor.getColumnIndexOrThrow(WaypointsColumns.MINELEVATION);
int idxMaxElevation =
cursor.getColumnIndexOrThrow(WaypointsColumns.MAXELEVATION);
int idxElevationGain =
cursor.getColumnIndexOrThrow(WaypointsColumns.ELEVATIONGAIN);
int idxMinGrade = cursor.getColumnIndexOrThrow(WaypointsColumns.MINGRADE);
int idxMaxGrade = cursor.getColumnIndexOrThrow(WaypointsColumns.MAXGRADE);
int idxLatitude = cursor.getColumnIndexOrThrow(WaypointsColumns.LATITUDE);
int idxLongitude = cursor.getColumnIndexOrThrow(WaypointsColumns.LONGITUDE);
int idxAltitude = cursor.getColumnIndexOrThrow(WaypointsColumns.ALTITUDE);
int idxTime = cursor.getColumnIndexOrThrow(WaypointsColumns.TIME);
int idxBearing = cursor.getColumnIndexOrThrow(WaypointsColumns.BEARING);
int idxAccuracy = cursor.getColumnIndexOrThrow(WaypointsColumns.ACCURACY);
int idxSpeed = cursor.getColumnIndexOrThrow(WaypointsColumns.SPEED);
Waypoint waypoint = new Waypoint();
TripStatistics stats = waypoint.getStatistics();
if (!cursor.isNull(idxId)) {
waypoint.setId(cursor.getLong(idxId));
}
if (!cursor.isNull(idxName)) {
waypoint.setName(cursor.getString(idxName));
}
if (!cursor.isNull(idxDescription)) {
waypoint.setDescription(cursor.getString(idxDescription));
}
if (!cursor.isNull(idxCategory)) {
waypoint.setCategory(cursor.getString(idxCategory));
}
if (!cursor.isNull(idxIcon)) {
waypoint.setIcon(cursor.getString(idxIcon));
}
if (!cursor.isNull(idxTrackId)) {
waypoint.setTrackId(cursor.getLong(idxTrackId));
}
if (!cursor.isNull(idxType)) {
waypoint.setType(cursor.getInt(idxType));
}
if (!cursor.isNull(idxLength)) {
waypoint.setLength(cursor.getDouble(idxLength));
}
if (!cursor.isNull(idxDuration)) {
waypoint.setDuration(cursor.getLong(idxDuration));
}
if (!cursor.isNull(idxStartTime)) {
stats.setStartTime(cursor.getLong(idxStartTime));
}
if (!cursor.isNull(idxStartId)) {
waypoint.setStartId(cursor.getLong(idxStartId));
}
if (!cursor.isNull(idxStopId)) {
waypoint.setStopId(cursor.getLong(idxStopId));
}
if (!cursor.isNull(idxTotalDistance)) {
stats.setTotalDistance(cursor.getFloat(idxTotalDistance));
}
if (!cursor.isNull(idxTotalTime)) {
stats.setTotalTime(cursor.getLong(idxTotalTime));
}
if (!cursor.isNull(idxMovingTime)) {
stats.setMovingTime(cursor.getLong(idxMovingTime));
}
if (!cursor.isNull(idxMaxSpeed)) {
stats.setMaxSpeed(cursor.getFloat(idxMaxSpeed));
}
if (!cursor.isNull(idxMinElevation)) {
stats.setMinElevation(cursor.getFloat(idxMinElevation));
}
if (!cursor.isNull(idxMaxElevation)) {
stats.setMaxElevation(cursor.getFloat(idxMaxElevation));
}
if (!cursor.isNull(idxElevationGain)) {
stats.setTotalElevationGain(cursor.getFloat(idxElevationGain));
}
if (!cursor.isNull(idxMinGrade)) {
stats.setMinGrade(cursor.getFloat(idxMinGrade));
}
if (!cursor.isNull(idxMaxGrade)) {
stats.setMaxGrade(cursor.getFloat(idxMaxGrade));
}
Location location = new Location("");
if (!cursor.isNull(idxLatitude) && !cursor.isNull(idxLongitude)) {
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
location.setLongitude(1. * cursor.getInt(idxLongitude) / 1E6);
}
if (!cursor.isNull(idxAltitude)) {
location.setAltitude(cursor.getFloat(idxAltitude));
}
if (!cursor.isNull(idxTime)) {
location.setTime(cursor.getLong(idxTime));
}
if (!cursor.isNull(idxBearing)) {
location.setBearing(cursor.getFloat(idxBearing));
}
if (!cursor.isNull(idxSpeed)) {
location.setSpeed(cursor.getFloat(idxSpeed));
}
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
}
waypoint.setLocation(location);
return waypoint;
}
@Override
public void deleteAllTracks() {
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
}
@Override
public void deleteTrack(long trackId) {
Track track = getTrack(trackId);
if (track != null) {
contentResolver.delete(TrackPointsColumns.CONTENT_URI,
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
null);
}
contentResolver.delete(WaypointsColumns.CONTENT_URI,
WaypointsColumns.TRACKID + "=" + trackId, null);
contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
}
@Override
public void deleteWaypoint(long waypointId,
DescriptionGenerator descriptionGenerator) {
final Waypoint deletedWaypoint = getWaypoint(waypointId);
if (deletedWaypoint != null
&& deletedWaypoint.getType() == Waypoint.TYPE_STATISTICS) {
final Waypoint nextWaypoint =
getNextStatisticsWaypointAfter(deletedWaypoint);
if (nextWaypoint != null) {
Log.d(MyTracksProvider.TAG, "Correcting marker " + nextWaypoint.getId()
+ " after deleted marker " + deletedWaypoint.getId());
nextWaypoint.getStatistics().merge(deletedWaypoint.getStatistics());
nextWaypoint.setDescription(
descriptionGenerator.generateWaypointDescription(nextWaypoint));
if (!updateWaypoint(nextWaypoint)) {
Log.w(MyTracksProvider.TAG, "Update of marker was unsuccessful.");
}
} else {
Log.d(MyTracksProvider.TAG,
"No statistics marker after the deleted one was found.");
}
}
contentResolver.delete(
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
}
@Override
public Waypoint getNextStatisticsWaypointAfter(Waypoint waypoint) {
final String selection = WaypointsColumns._ID + ">" + waypoint.getId()
+ " AND " + WaypointsColumns.TRACKID + "=" + waypoint.getTrackId()
+ " AND " + WaypointsColumns.TYPE + "=" + Waypoint.TYPE_STATISTICS;
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
Cursor cursor = null;
try {
cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
selection,
null /*selectionArgs*/,
sortOrder);
if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
@Override
public boolean updateWaypoint(Waypoint waypoint) {
try {
final int rows = contentResolver.update(
WaypointsColumns.CONTENT_URI,
createContentValues(waypoint),
"_id=" + waypoint.getId(),
null /*selectionArgs*/);
return rows == 1;
} catch (RuntimeException e) {
Log.e(MyTracksProvider.TAG, "Caught unexpected exception.", e);
}
return false;
}
/**
* Finds a locations from the provider by the given selection.
*
* @param select a selection argument that identifies a unique location
* @return the fist location matching, or null if not found
*/
private Location findLocationBy(String select) {
Cursor cursor = null;
try {
cursor = contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createLocation(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpeceted exception.", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* Finds a track from the provider by the given selection.
*
* @param select a selection argument that identifies a unique track
* @return the first track matching, or null if not found
*/
private Track findTrackBy(String select) {
Cursor cursor = null;
try {
cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, select, null, null);
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
@Override
public Location getLastLocation() {
return findLocationBy("_id=(select max(_id) from trackpoints)");
}
@Override
public Waypoint getFirstWaypoint(long trackId) {
Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"trackid=" + trackId,
null /*selectionArgs*/,
"_id LIMIT 1");
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
}
return null;
}
@Override
public Waypoint getWaypoint(long waypointId) {
Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
null /*projection*/,
"_id=" + waypointId,
null /*selectionArgs*/,
null /*sortOrder*/);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
}
return null;
}
@Override
public long getLastLocationId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
TrackPointsColumns.CONTENT_URI,
projection,
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
null /*selectionArgs*/,
null /*sortOrder*/);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getLong(
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
}
return -1;
}
@Override
public long getFirstWaypointId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
projection,
"trackid=" + trackId,
null /*selectionArgs*/,
"_id LIMIT 1" /*sortOrder*/);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getLong(
cursor.getColumnIndexOrThrow(WaypointsColumns._ID));
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
}
return -1;
}
@Override
public long getLastWaypointId(long trackId) {
final String[] projection = {"_id"};
Cursor cursor = contentResolver.query(
WaypointsColumns.CONTENT_URI,
projection,
WaypointsColumns.TRACKID + "=" + trackId,
null /*selectionArgs*/,
"_id DESC LIMIT 1" /*sortOrder*/);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getLong(
cursor.getColumnIndexOrThrow(WaypointsColumns._ID));
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
}
return -1;
}
@Override
public Track getLastTrack() {
Cursor cursor = null;
try {
cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
@Override
public long getLastTrackId() {
String[] proj = { TracksColumns._ID };
Cursor cursor = contentResolver.query(
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return cursor.getLong(
cursor.getColumnIndexOrThrow(TracksColumns._ID));
}
} finally {
cursor.close();
}
}
return -1;
}
@Override
public Location getLocation(long id) {
String selection = TrackPointsColumns._ID + "=" + id;
return findLocationBy(selection);
}
@Override
public Cursor getLocationsCursor(long trackId, long minTrackPointId,
int maxLocations, boolean descending) {
String selection;
if (minTrackPointId > 0) {
selection = String.format("%s=%d AND %s>=%d",
TrackPointsColumns.TRACKID, trackId,
TrackPointsColumns._ID, minTrackPointId);
} else {
selection = String.format("%s=%d",
TrackPointsColumns.TRACKID, trackId);
}
String sortOrder = "_id " + (descending ? "DESC" : "ASC");
if (maxLocations > 0) {
sortOrder += " LIMIT " + maxLocations;
}
return contentResolver.query(
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@Override
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
int maxWaypoints) {
String selection;
if (minWaypointId > 0) {
selection = String.format("%s=%d AND %s>=%d",
WaypointsColumns.TRACKID, trackId,
WaypointsColumns._ID, minWaypointId);
} else {
selection = String.format("%s=%d",
WaypointsColumns.TRACKID, trackId);
}
String sortOrder = "_id ASC";
if (maxWaypoints > 0) {
sortOrder += " LIMIT " + maxWaypoints;
}
return contentResolver.query(
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
}
@Override
public Track getTrack(long id) {
String select = TracksColumns._ID + "=" + id;
return findTrackBy(select);
}
@Override
public List<Track> getAllTracks() {
Cursor cursor = getTracksCursor(null);
if (cursor == null || !cursor.moveToFirst()) {
return new ArrayList<Track>();
}
List<Track> tracks = new ArrayList<Track>(cursor.getCount());
do {
tracks.add(createTrack(cursor));
} while(cursor.moveToNext());
return tracks;
}
@Override
public long getTrackPoints(Track track, int maxPoints) {
long lastId = -1;
Cursor cursor = getLocationsCursor(track.getId(), -1, maxPoints, true);
if (cursor == null) {
Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!");
return lastId;
}
try {
final int idColumnIdx =
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
if (cursor.moveToLast()) {
do {
Location location = createLocation(cursor);
if (location == null) {
continue;
}
track.addLocation(location);
lastId = cursor.getLong(idColumnIdx);
} while (cursor.moveToPrevious());
}
} catch (RuntimeException e) {
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
} finally {
cursor.close();
}
return lastId;
}
@Override
public Cursor getTracksCursor(String selection) {
Cursor cursor = contentResolver.query(
TracksColumns.CONTENT_URI, null, selection, null, "_id");
return cursor;
}
@Override
public Uri insertTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
return contentResolver.insert(TracksColumns.CONTENT_URI,
createContentValues(track));
}
@Override
public Uri insertTrackPoint(Location location, long trackId) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
createContentValues(location, trackId));
}
@Override
public int bulkInsertTrackPoints(Location[] locations, int length, long trackId) {
if (length == -1) { length = locations.length; }
ContentValues[] values = new ContentValues[length];
for (int i = 0; i < length; i++) {
values[i] = createContentValues(locations[i], trackId);
}
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
}
@Override
public Uri insertWaypoint(Waypoint waypoint) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
waypoint.setId(-1);
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
createContentValues(waypoint));
}
@Override
public boolean trackExists(long id) {
Cursor cursor = null;
try {
final String[] projection = { TracksColumns._ID };
cursor = contentResolver.query(
TracksColumns.CONTENT_URI,
projection,
TracksColumns._ID + "=" + id/*selection*/,
null/*selectionArgs*/,
null/*sortOrder*/);
if (cursor != null && cursor.moveToNext()) {
return true;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return false;
}
@Override
public void updateTrack(Track track) {
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
contentResolver.update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
}
@@ -1,199 +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.content;
import com.google.android.apps.mytracks.stats.TripStatistics;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
/**
* A class representing a (GPS) Track.
*
* TODO: hashCode and equals
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public class Track implements Parcelable {
/**
* Creator for a Track object
*/
public static class Creator implements Parcelable.Creator<Track> {
public Track createFromParcel(Parcel source) {
ClassLoader classLoader = getClass().getClassLoader();
Track track = new Track();
track.id = source.readLong();
track.name = source.readString();
track.description = source.readString();
track.mapId = source.readString();
track.category = source.readString();
track.startId = source.readLong();
track.stopId = source.readLong();
track.stats = source.readParcelable(classLoader);
track.numberOfPoints = source.readInt();
for (int i = 0; i < track.numberOfPoints; ++i) {
Location loc = source.readParcelable(classLoader);
track.locations.add(loc);
}
return track;
}
public Track[] newArray(int size) {
return new Track[size];
}
}
public static final Creator CREATOR = new Creator();
/**
* The track points (which may not have been loaded).
*/
private ArrayList<Location> locations = new ArrayList<Location>();
/**
* The number of location points (present even if the points themselves were
* not loaded).
*/
private int numberOfPoints = 0;
private long id = -1;
private String name = "";
private String description = "";
private String mapId = "";
private long startId = -1;
private long stopId = -1;
private String category = "";
private TripStatistics stats = new TripStatistics();
public Track() {
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);
dest.writeString(description);
dest.writeString(mapId);
dest.writeString(category);
dest.writeLong(startId);
dest.writeLong(stopId);
dest.writeParcelable(stats, 0);
dest.writeInt(numberOfPoints);
for (int i = 0; i < numberOfPoints; ++i) {
dest.writeParcelable(locations.get(i), 0);
}
}
// Getters and setters:
//---------------------
public int describeContents() {
return 0;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getStartId() {
return startId;
}
public void setStartId(long startId) {
this.startId = startId;
}
public long getStopId() {
return stopId;
}
public void setStopId(long stopId) {
this.stopId = stopId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getMapId() {
return mapId;
}
public void setMapId(String mapId) {
this.mapId = mapId;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public int getNumberOfPoints() {
return numberOfPoints;
}
public void setNumberOfPoints(int numberOfPoints) {
this.numberOfPoints = numberOfPoints;
}
public void addLocation(Location l) {
locations.add(l);
}
public ArrayList<Location> getLocations() {
return locations;
}
public void setLocations(ArrayList<Location> locations) {
this.locations = locations;
}
public TripStatistics getStatistics() {
return stats;
}
public void setStatistics(TripStatistics stats) {
this.stats = stats;
}
}
@@ -1,57 +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.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* Defines the URI for the track points provider and the available column names
* and content types.
*
* @author Leif Hendrik Wilden
*/
public interface TrackPointsColumns extends BaseColumns {
public static final Uri CONTENT_URI =
Uri.parse("content://com.google.android.maps.mytracks/trackpoints");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.google.trackpoint";
public static final String CONTENT_ITEMTYPE =
"vnd.android.cursor.item/vnd.google.trackpoint";
public static final String DEFAULT_SORT_ORDER = "_id";
/* All columns */
public static final String TRACKID = "trackid";
public static final String LATITUDE = "latitude";
public static final String LONGITUDE = "longitude";
public static final String ALTITUDE = "elevation";
public static final String BEARING = "bearing";
public static final String TIME = "time";
public static final String ACCURACY = "accuracy";
public static final String SPEED = "speed";
/** Columns that go into the backup. */
public static final String[] BACKUP_COLUMNS =
{ _ID, TRACKID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
ACCURACY, SPEED };
/** Types of the columns that go into the backup. */
public static final byte[] BACKUP_COLUMN_TYPES =
{ LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID };
}
@@ -1,76 +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.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* Defines the URI for the tracks provider and the available column names
* and content types.
*
* @author Leif Hendrik Wilden
*/
public interface TracksColumns extends BaseColumns {
public static final Uri CONTENT_URI =
Uri.parse("content://com.google.android.maps.mytracks/tracks");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.google.track";
public static final String CONTENT_ITEMTYPE =
"vnd.android.cursor.item/vnd.google.track";
public static final String DEFAULT_SORT_ORDER = "_id";
/* All columns */
public static final String NAME = "name";
public static final String DESCRIPTION = "description";
public static final String CATEGORY = "category";
public static final String STARTID = "startid";
public static final String STOPID = "stopid";
public static final String STARTTIME = "starttime";
public static final String STOPTIME = "stoptime";
public static final String NUMPOINTS = "numpoints";
public static final String TOTALDISTANCE = "totaldistance";
public static final String TOTALTIME = "totaltime";
public static final String MOVINGTIME = "movingtime";
public static final String AVGSPEED = "avgspeed";
public static final String AVGMOVINGSPEED = "avgmovingspeed";
public static final String MAXSPEED = "maxspeed";
public static final String MINELEVATION = "minelevation";
public static final String MAXELEVATION = "maxelevation";
public static final String ELEVATIONGAIN = "elevationgain";
public static final String MINGRADE = "mingrade";
public static final String MAXGRADE = "maxgrade";
public static final String MINLAT = "minlat";
public static final String MAXLAT = "maxlat";
public static final String MINLON = "minlon";
public static final String MAXLON = "maxlon";
public static final String MAPID = "mapid";
public static final String[] BACKUP_COLUMNS = {
_ID, NAME, DESCRIPTION, CATEGORY, STARTID, STOPID, STARTTIME, STOPTIME,
NUMPOINTS, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED, AVGMOVINGSPEED,
MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN, MINGRADE, MAXGRADE,
MINLAT, MAXLAT, MINLON, MAXLON };
public static final byte[] BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
}
@@ -1,2 +0,0 @@
package com.google.android.apps.mytracks.content;
parcelable Waypoint;
@@ -1,223 +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.content;
import com.google.android.apps.mytracks.stats.TripStatistics;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A way point. It has a location, meta data such as name, description,
* category, and icon, plus it can store track statistics for a "sub-track".
*
* TODO: hashCode and equals
*
* @author Leif Hendrik Wilden
* @author Rodrigo Damazio
*/
public final class Waypoint implements Parcelable {
/**
* Creator for a Waypoint object
*/
public static class Creator implements Parcelable.Creator<Waypoint> {
public Waypoint createFromParcel(Parcel source) {
ClassLoader classLoader = getClass().getClassLoader();
Waypoint waypoint = new Waypoint();
waypoint.id = source.readLong();
waypoint.name = source.readString();
waypoint.description = source.readString();
waypoint.category = source.readString();
waypoint.icon = source.readString();
waypoint.trackId = source.readLong();
waypoint.type = source.readInt();
waypoint.startId = source.readLong();
waypoint.stopId = source.readLong();
waypoint.stats = source.readParcelable(classLoader);
byte hasLocation = source.readByte();
if (hasLocation > 0) {
waypoint.location = source.readParcelable(classLoader);
}
return waypoint;
}
public Waypoint[] newArray(int size) {
return new Waypoint[size];
}
}
public static final Creator CREATOR = new Creator();
public static final int TYPE_WAYPOINT = 0;
public static final int TYPE_STATISTICS = 1;
private long id = -1;
private String name = "";
private String description = "";
private String category = "";
private String icon = "";
private long trackId = -1;
private int type = 0;
private Location location;
/** Start track point id */
private long startId = -1;
/** Stop track point id */
private long stopId = -1;
private TripStatistics stats = new TripStatistics();
/** The length of the track, without smoothing. */
private double length;
/** The total duration of the track (not from the last waypoint) */
private long duration;
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);
dest.writeString(description);
dest.writeString(category);
dest.writeString(icon);
dest.writeLong(trackId);
dest.writeInt(type);
dest.writeLong(startId);
dest.writeLong(stopId);
dest.writeParcelable(stats, 0);
dest.writeByte(location == null ? (byte) 0 : (byte) 1);
if (location != null) {
dest.writeParcelable(location, 0);
}
}
// Getters and setters:
//---------------------
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Location getLocation() {
return location;
}
public void setTrackId(long trackId) {
this.trackId = trackId;
}
public int describeContents() {
return 0;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getTrackId() {
return trackId;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public long getStartId() {
return startId;
}
public void setStartId(long startId) {
this.startId = startId;
}
public long getStopId() {
return stopId;
}
public void setStopId(long stopId) {
this.stopId = stopId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public void setLocation(Location location) {
this.location = location;
}
public TripStatistics getStatistics() {
return stats;
}
public void setStatistics(TripStatistics stats) {
this.stats = stats;
}
// WARNING: These fields are used for internal state keeping. You probably
// want to look at getStatistics instead.
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
}
@@ -1,86 +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.content;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* Defines the URI for the tracks provider and the available column names
* and content types.
*
* @author Leif Hendrik Wilden
*/
public interface WaypointsColumns extends BaseColumns {
public static final Uri CONTENT_URI =
Uri.parse("content://com.google.android.maps.mytracks/waypoints");
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.google.waypoint";
public static final String CONTENT_ITEMTYPE =
"vnd.android.cursor.item/vnd.google.waypoint";
public static final String DEFAULT_SORT_ORDER = "_id";
/* All columns */
public static final String NAME = "name";
public static final String DESCRIPTION = "description";
public static final String CATEGORY = "category";
public static final String ICON = "icon";
public static final String TRACKID = "trackid";
public static final String TYPE = "type";
public static final String LENGTH = "length";
public static final String DURATION = "duration";
public static final String STARTTIME = "starttime";
public static final String STARTID = "startid";
public static final String STOPID = "stopid";
public static final String LATITUDE = "latitude";
public static final String LONGITUDE = "longitude";
public static final String ALTITUDE = "elevation";
public static final String BEARING = "bearing";
public static final String TIME = "time";
public static final String ACCURACY = "accuracy";
public static final String SPEED = "speed";
public static final String TOTALDISTANCE = "totaldistance";
public static final String TOTALTIME = "totaltime";
public static final String MOVINGTIME = "movingtime";
public static final String AVGSPEED = "avgspeed";
public static final String AVGMOVINGSPEED = "avgmovingspeed";
public static final String MAXSPEED = "maxspeed";
public static final String MINELEVATION = "minelevation";
public static final String MAXELEVATION = "maxelevation";
public static final String ELEVATIONGAIN = "elevationgain";
public static final String MINGRADE = "mingrade";
public static final String MAXGRADE = "maxgrade";
public static final String[] BACKUP_COLUMNS = {
_ID, TRACKID, NAME, DESCRIPTION, CATEGORY, ICON, TYPE, LENGTH, DURATION,
STARTTIME, STARTID, STOPID, LATITUDE, LONGITUDE, ALTITUDE, BEARING, TIME,
ACCURACY, SPEED, TOTALDISTANCE, TOTALTIME, MOVINGTIME, AVGSPEED,
AVGMOVINGSPEED, MAXSPEED, MINELEVATION, MAXELEVATION, ELEVATIONGAIN,
MINGRADE, MAXGRADE };
public static final byte[] BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
STRING_TYPE_ID, STRING_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID };
}
@@ -342,8 +342,14 @@ public class GpxImporter extends DefaultHandler {
// create new location and set attributes
Location loc = new Location(LocationManager.GPS_PROVIDER);
loc.setLatitude(Double.parseDouble(latitude));
loc.setLongitude(Double.parseDouble(longitude));
try {
loc.setLatitude(Double.parseDouble(latitude));
loc.setLongitude(Double.parseDouble(longitude));
} catch (NumberFormatException e) {
String msg = createErrorMessage(
"Unable to parse lat/long: " + latitude + "/" + longitude);
throw new SAXException(msg, e);
}
return loc;
}
@@ -472,9 +478,14 @@ public class GpxImporter extends DefaultHandler {
}
}
private void onAltitudeElementEnd() {
private void onAltitudeElementEnd() throws SAXException {
if (location != null) {
location.setAltitude(Double.parseDouble(content));
try {
location.setAltitude(Double.parseDouble(content));
} catch (NumberFormatException e) {
String msg = createErrorMessage("Unable to parse altitude: " + content);
throw new SAXException(msg, e);
}
}
}
@@ -15,8 +15,11 @@
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
import android.location.Location;
@@ -141,6 +144,37 @@ public class TcxTrackWriter implements TrackFormatWriter {
pw.print(location.getAltitude());
pw.println("</AltitudeMeters>");
if (location instanceof MyTracksLocation) {
SensorDataSet sensorData = ((MyTracksLocation) location).getSensorDataSet();
if (sensorData != null) {
if (sensorData.hasHeartRate()
&& sensorData.getHeartRate().getState() == Sensor.SensorState.SENDING
&& sensorData.getHeartRate().hasValue()) {
pw.print(" <HeartRateBpm>");
pw.print("<Value>");
pw.print(sensorData.getHeartRate().getValue());
pw.print("</Value>");
pw.println("</HeartRateBpm>");
}
if (sensorData.hasPower()
&& sensorData.getPower().getState() == Sensor.SensorState.SENDING
&& sensorData.getPower().hasValue()) {
pw.print(" <Extensions>");
pw.print("<TPX xmlns=\"http://www.garmin.com/xmlschemas/ActivityExtension/v2\">");
pw.print("<Watts>");
pw.print(sensorData.getPower().getValue());
pw.print("</Watts>");
pw.println("</TPX></Extensions>");
}
if (sensorData.hasCadence()
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
pw.print(" <Cadence>");
pw.print(sensorData.getCadence().getValue());
pw.println("</Cadence>");
}
}
}
pw.println(" </Trackpoint>");
}
@@ -0,0 +1,67 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.io.backup;
import static com.google.android.apps.mytracks.content.ContentTypeIds.*;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.WaypointsColumns;
public class BackupColumns {
/** Columns that go into the backup. */
public static final String[] POINTS_BACKUP_COLUMNS =
{ TrackPointsColumns._ID, TrackPointsColumns.TRACKID, TrackPointsColumns.LATITUDE,
TrackPointsColumns.LONGITUDE, TrackPointsColumns.ALTITUDE, TrackPointsColumns.BEARING,
TrackPointsColumns.TIME, TrackPointsColumns.ACCURACY, TrackPointsColumns.SPEED };
public static final byte[] POINTS_BACKUP_COLUMN_TYPES =
{ LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID };
public static final String[] TRACKS_BACKUP_COLUMNS = {
TracksColumns._ID, TracksColumns.NAME, TracksColumns.DESCRIPTION, TracksColumns.CATEGORY,
TracksColumns.STARTID, TracksColumns.STOPID, TracksColumns.STARTTIME, TracksColumns.STOPTIME,
TracksColumns.NUMPOINTS, TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME,
TracksColumns.MOVINGTIME, TracksColumns.AVGSPEED, TracksColumns.AVGMOVINGSPEED,
TracksColumns.MAXSPEED, TracksColumns.MINELEVATION, TracksColumns.MAXELEVATION,
TracksColumns.ELEVATIONGAIN, TracksColumns.MINGRADE, TracksColumns.MAXGRADE,
TracksColumns.MINLAT, TracksColumns.MAXLAT, TracksColumns.MINLON, TracksColumns.MAXLON };
public static final byte[] TRACKS_BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID };
public static final String[] WAYPOINTS_BACKUP_COLUMNS = {
WaypointsColumns._ID, WaypointsColumns.TRACKID, WaypointsColumns.NAME,
WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY, WaypointsColumns.ICON,
WaypointsColumns.TYPE, WaypointsColumns.LENGTH, WaypointsColumns.DURATION,
WaypointsColumns.STARTTIME, WaypointsColumns.STARTID, WaypointsColumns.STOPID,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE, WaypointsColumns.ALTITUDE,
WaypointsColumns.BEARING, WaypointsColumns.TIME, WaypointsColumns.ACCURACY,
WaypointsColumns.SPEED, WaypointsColumns.TOTALDISTANCE, WaypointsColumns.TOTALTIME,
WaypointsColumns.MOVINGTIME, WaypointsColumns.AVGSPEED, WaypointsColumns.AVGMOVINGSPEED,
WaypointsColumns.MAXSPEED, WaypointsColumns.MINELEVATION, WaypointsColumns.MAXELEVATION,
WaypointsColumns.ELEVATIONGAIN, WaypointsColumns.MINGRADE, WaypointsColumns.MAXGRADE };
public static final byte[] WAYPOINTS_BACKUP_COLUMN_TYPES = {
LONG_TYPE_ID, LONG_TYPE_ID, STRING_TYPE_ID, STRING_TYPE_ID,
STRING_TYPE_ID, STRING_TYPE_ID, INT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID,
LONG_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, INT_TYPE_ID, INT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, LONG_TYPE_ID, LONG_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID, FLOAT_TYPE_ID,
FLOAT_TYPE_ID };
}
@@ -153,16 +153,16 @@ class ExternalFileBackup {
// Create all the auxiliary classes that will do the writing
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
DatabaseDumper trackDumper = new DatabaseDumper(
TracksColumns.BACKUP_COLUMNS,
TracksColumns.BACKUP_COLUMN_TYPES,
BackupColumns.TRACKS_BACKUP_COLUMNS,
BackupColumns.TRACKS_BACKUP_COLUMN_TYPES,
false);
DatabaseDumper waypointDumper = new DatabaseDumper(
WaypointsColumns.BACKUP_COLUMNS,
WaypointsColumns.BACKUP_COLUMN_TYPES,
BackupColumns.WAYPOINTS_BACKUP_COLUMNS,
BackupColumns.WAYPOINTS_BACKUP_COLUMN_TYPES,
false);
DatabaseDumper pointDumper = new DatabaseDumper(
TrackPointsColumns.BACKUP_COLUMNS,
TrackPointsColumns.BACKUP_COLUMN_TYPES,
BackupColumns.POINTS_BACKUP_COLUMNS,
BackupColumns.POINTS_BACKUP_COLUMN_TYPES,
false);
// Open the target for writing
@@ -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.services;
import com.google.android.apps.mytracks.content.Waypoint;
/**
* MyTracks service.
* This service is the process that actually records and manages tracks.
*/
interface ITrackRecordingService {
/**
* Starts recording a new track.
*
* @return the track ID of the new track
*/
long startNewTrack();
/**
* Checks and returns whether we're currently recording a track.
*/
boolean isRecording();
/**
* Returns the track ID of the track currently being recorded, or -1 if none
* is being recorded. This ID can then be used to read track data from the
* content source.
*/
long getRecordingTrackId();
/**
* Inserts a waypoint marker in the track being recorded.
*
* @param waypoint the waypoint to insert
* @return the unique ID of the inserted marker
*/
long insertWaypointMarker(in Waypoint waypoint);
/**
* Inserts a statistics marker in the track being recorded.
*
* @param location the location at which to insert the marker
* @return the unique ID of the inserted marker
*/
long insertStatisticsMarker(in Location location);
/**
* Inserts a location in the track being recorded.
*
* When recording, locations detected by the GPS are already automatically
* added to the track, so this should be used only for adding special points
* or for testing.
*
* @param loc the location to insert
*/
void recordLocation(in Location loc);
/**
* Stops recording the current track.
*/
void endCurrentTrack();
/**
* Returns whether at least one track has been or is being recorded (and was
* not deleted).
*/
boolean hasRecorded();
/**
* Deletes all the stored tracks.
*/
void deleteAllTracks();
}
@@ -40,7 +40,6 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
private final String minRecordingIntervalKey;
private final String minRequiredAccuracyKey;
private final String recordingTrackKey;
private final String signalSamplingFrequencyKey;
private final String splitFrequencyKey;
public PreferenceManager(TrackRecordingService service) {
@@ -72,8 +71,6 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
service.getString(R.string.min_required_accuracy_key);
recordingTrackKey =
service.getString(R.string.recording_track_key);
signalSamplingFrequencyKey =
service.getString(R.string.signal_sampling_frequency_key);
splitFrequencyKey =
service.getString(R.string.split_frequency_key);
@@ -163,10 +160,6 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
service.getSplitManager().setSplitFrequency(
sharedPreferences.getInt(splitFrequencyKey, 0));
}
if (key == null || key.equals(signalSamplingFrequencyKey)) {
service.getSignalManager().setFrequency(
sharedPreferences.getInt(signalSamplingFrequencyKey, -1), service);
}
if (key == null || key.equals(metricUnitsKey)) {
service.getSplitManager().setMetricUnits(
sharedPreferences.getBoolean(metricUnitsKey, true));
@@ -1,195 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Waypoint;
import android.content.Context;
import android.location.Location;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.util.List;
/**
* A class to monitor the network signal strength.
*
* @author Sandor Dornbush
*/
public class SignalStrengthTask extends PhoneStateListener
implements PeriodicTask {
private final Context context;
private TelephonyManager manager;
private int signalStrength = -1;
public SignalStrengthTask(Context c) {
context = c;
}
@Override
public void start() {
manager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (manager == null) {
Log.e(MyTracksConstants.TAG, "Cannot get telephony manager.");
} else {
manager.listen(this, getListenEvents());
}
}
protected int getListenEvents() {
return PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
}
@Override
public void onSignalStrengthChanged(int asu) {
this.signalStrength = asu;
Log.d(MyTracksConstants.TAG, "Signal Strength: " + signalStrength);
}
@Override
public void run(TrackRecordingService service) {
Log.d(MyTracksConstants.TAG, "Adding signal marker");
Location location = service.getLastLocation();
if (manager == null) {
Log.d(MyTracksConstants.TAG, "Adding signal marker: marker null");
return;
}
if (location == null) {
Log.d(MyTracksConstants.TAG, "Adding signal marker: location null");
return;
}
Waypoint wpt = new Waypoint();
wpt.setName("Signal Strength");
wpt.setType(Waypoint.TYPE_WAYPOINT);
wpt.setTrackId(service.getRecordingTrackId());
int networkType = manager.getNetworkType();
wpt.setIcon(getIcon(networkType));
wpt.setLocation(location);
wpt.setDescription(getDescription());
long waypointId = service.insertWaypointMarker(wpt);
if (waypointId >= 0) {
Log.d(MyTracksConstants.TAG, "Added signal marker");
} else {
Log.e(MyTracksConstants.TAG, "Cannot insert waypoint marker?");
}
}
/**
* Gets a human readable description for the network type.
*
* @param type The integer constant for the network type
* @return A human readable description of the network type
*/
protected String getTypeAsString(int type) {
switch (type) {
case TelephonyManager.NETWORK_TYPE_EDGE:
return "EDGE";
case TelephonyManager.NETWORK_TYPE_GPRS:
return "GPRS";
case TelephonyManager.NETWORK_TYPE_UMTS:
return "UMTS";
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return "UNKNOWN";
}
}
/**
* Builds a description for the current signal strength.
*
* @return A human readable description of the network state
*/
private String getDescription() {
StringBuffer sb = new StringBuffer();
sb.append(getStrengthAsString());
sb.append("Network Type: ");
sb.append(getTypeAsString(manager.getNetworkType()));
sb.append('\n');
sb.append("Operator: ");
sb.append(manager.getNetworkOperatorName());
sb.append(" / ");
sb.append(manager.getNetworkOperator());
sb.append('\n');
sb.append("Roaming: ");
sb.append(manager.isNetworkRoaming());
sb.append('\n');
List<NeighboringCellInfo> infos = manager.getNeighboringCellInfo();
Log.i(MyTracksConstants.TAG, "Found " + infos.size() + " cells.");
if (infos.size() > 0) {
sb.append("Neighbors: ");
for (NeighboringCellInfo info : infos) {
sb.append(info.toString());
sb.append(' ');
}
sb.append('\n');
}
CellLocation cell = manager.getCellLocation();
if (cell != null) {
sb.append("Cell: ");
sb.append(cell.toString());
sb.append('\n');
}
return sb.toString();
}
/**
* Gets the url for the waypoint icon for the current network type.
*
* @param type The network type
* @return A url to a image to use as the waypoint icon
*/
protected String getIcon(int type) {
switch (type) {
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_EDGE:
return "http://maps.google.com/mapfiles/ms/micons/green.png";
case TelephonyManager.NETWORK_TYPE_UMTS:
return "http://maps.google.com/mapfiles/ms/micons/blue.png";
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return "http://maps.google.com/mapfiles/ms/micons/red.png";
}
}
@Override
public void shutdown() {
if (manager != null) {
manager.listen(this, PhoneStateListener.LISTEN_NONE);
manager = null;
}
}
public String getStrengthAsString() {
return "Strength: " + signalStrength + "\n";
}
protected Context getContext() {
return context;
}
}
@@ -1,48 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.util.ApiFeatures;
import android.content.Context;
import android.util.Log;
/**
* Factory for producing a proper {@link SignalStrengthTask} according to the
* current API level.
*
* @author Rodrigo Damazio
*/
public class SignalStrengthTaskFactory {
private final boolean hasModernSignalStrength;
SignalStrengthTaskFactory(ApiFeatures apiFeatures) {
this.hasModernSignalStrength = apiFeatures.hasModernSignalStrength();
}
public PeriodicTask create(Context context) {
if (hasModernSignalStrength) {
Log.d(MyTracksConstants.TAG,
"TrackRecordingService using modern signal strength api.");
return new SignalStrengthTaskModern(context);
} else {
Log.w(MyTracksConstants.TAG,
"TrackRecordingService using legacy signal strength api.");
return new SignalStrengthTask(context);
}
}
}
@@ -1,155 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
/**
* A class to monitor the network signal strength.
*
* @author Sandor Dornbush
*/
public class SignalStrengthTaskModern extends SignalStrengthTask {
private SignalStrength signalStrength = null;
public SignalStrengthTaskModern(Context c) {
super(c);
}
@Override
protected int getListenEvents() {
return PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
}
/**
* Gets a human readable description for the network type.
*
* @param type The integer constant for the network type
* @return A human readable description of the network type
*/
@Override
protected String getTypeAsString(int type) {
switch (type) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
return "1xRTT";
case TelephonyManager.NETWORK_TYPE_CDMA:
return "CDMA";
case TelephonyManager.NETWORK_TYPE_EDGE:
return "EDGE";
case TelephonyManager.NETWORK_TYPE_EVDO_0:
return "EVDO 0";
case TelephonyManager.NETWORK_TYPE_EVDO_A:
return "EVDO A";
case TelephonyManager.NETWORK_TYPE_GPRS:
return "GPRS";
case TelephonyManager.NETWORK_TYPE_HSDPA:
return "HSDPA";
case TelephonyManager.NETWORK_TYPE_HSPA:
return "HSPA";
case TelephonyManager.NETWORK_TYPE_HSUPA:
return "HSUPA";
case TelephonyManager.NETWORK_TYPE_UMTS:
return "UMTS";
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return "UNKNOWN";
}
}
/**
* Gets the url for the waypoint icon for the current network type.
*
* @param type The network type
* @return A url to a image to use as the waypoint icon
*/
@Override
protected String getIcon(int type) {
switch (type) {
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_EDGE:
return "http://maps.google.com/mapfiles/ms/micons/green.png";
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_UMTS:
return "http://maps.google.com/mapfiles/ms/micons/blue.png";
case TelephonyManager.NETWORK_TYPE_UNKNOWN:
default:
return "http://maps.google.com/mapfiles/ms/micons/red.png";
}
}
@Override
public String getStrengthAsString() {
if (signalStrength == null) {
return "Strength: " + getContext().getString(R.string.unknown) + "\n";
}
StringBuffer sb = new StringBuffer();
if (signalStrength.isGsm()) {
appendSignal(signalStrength.getGsmSignalStrength(),
R.string.gsm_strength,
sb);
maybeAppendSignal(signalStrength.getGsmBitErrorRate(),
R.string.error_rate,
sb);
} else {
appendSignal(signalStrength.getCdmaDbm(), R.string.cdma_strength, sb);
appendSignal(signalStrength.getCdmaEcio() / 10.0, R.string.ecio, sb);
appendSignal(signalStrength.getEvdoDbm(), R.string.evdo_strength, sb);
appendSignal(signalStrength.getEvdoEcio() / 10.0, R.string.ecio, sb);
appendSignal(signalStrength.getEvdoSnr(),
R.string.signal_to_noise_ratio,
sb);
}
return sb.toString();
}
private void maybeAppendSignal(
int signal, int signalFormat, StringBuffer sb) {
if (signal > 0) {
sb.append(getContext().getString(signalFormat, signal));
}
}
private void appendSignal(int signal, int signalFormat, StringBuffer sb) {
sb.append(getContext().getString(signalFormat, signal));
sb.append("\n");
}
private void appendSignal(double signal, int signalFormat, StringBuffer sb) {
sb.append(getContext().getString(signalFormat, signal));
sb.append("\n");
}
@Override
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
Log.d(MyTracksConstants.TAG, "Signal Strength Modern: " + signalStrength);
this.signalStrength = signalStrength;
}
}
@@ -20,11 +20,16 @@ import static com.google.android.apps.mytracks.MyTracksConstants.RESUME_TRACK_EX
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
import com.google.android.apps.mytracks.services.sensors.SensorManager;
import com.google.android.apps.mytracks.services.sensors.SensorManagerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
import com.google.android.apps.mytracks.util.ApiFeatures;
@@ -69,7 +74,7 @@ public class TrackRecordingService extends Service implements LocationListener {
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
private NotificationManager notificationManager;
private LocationManager locationManager;
private WakeLock wakeLock;
@@ -109,9 +114,10 @@ public class TrackRecordingService extends Service implements LocationListener {
* Status announcer executer.
*/
private PeriodicTaskExecuter announcementExecuter;
private TaskExecuterManager signalManager;
private SplitManager splitManager;
private SensorManager sensorManager;
private PreferenceManager prefManager;
/**
@@ -215,7 +221,14 @@ public class TrackRecordingService extends Service implements LocationListener {
// Insert the new location:
try {
Uri pointUri = providerUtils.insertTrackPoint(location, trackId);
Location locationToInsert = location;
if (sensorManager != null && sensorManager.isEnabled()) {
SensorDataSet sd = sensorManager.getSensorDataSet();
if (sd != null && sensorManager.isDataValid()) {
locationToInsert = new MyTracksLocation(location, sd);
}
}
Uri pointUri = providerUtils.insertTrackPoint(locationToInsert, trackId);
int pointId = Integer.parseInt(pointUri.getLastPathSegment());
// Update the current track:
@@ -411,7 +424,6 @@ public class TrackRecordingService extends Service implements LocationListener {
statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
setUpAnnouncer();
signalManager.restore();
splitManager.restore();
length = 0;
lastValidLocation = null;
@@ -525,11 +537,15 @@ public class TrackRecordingService extends Service implements LocationListener {
if (lastLocation != null) {
distanceToLast = location.distanceTo(lastLocation);
}
boolean hasSensorData = sensorManager != null
&& sensorManager.isEnabled()
&& sensorManager.getSensorDataSet() != null
&& sensorManager.isDataValid();
// If the user has been stationary for two recording just record the first
// two and ignore the rest. This code will only have an effect if the
// maxRecordingDistance = 0
if (distanceToLast == 0) {
if (distanceToLast == 0 && !hasSensorData) {
if (isMoving) {
Log.d(MyTracksConstants.TAG, "Found two identical locations.");
isMoving = false;
@@ -549,7 +565,8 @@ public class TrackRecordingService extends Service implements LocationListener {
Log.d(MyTracksConstants.TAG,
"Not recording. More than two identical locations.");
}
} else if (distanceToLastRecorded > minRecordingDistance) {
} else if (distanceToLastRecorded > minRecordingDistance
|| hasSensorData) {
if (lastLocation != null && !isMoving) {
// Last location was the last stationary location. Need to go back and
// add it.
@@ -629,14 +646,11 @@ public class TrackRecordingService extends Service implements LocationListener {
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
splitManager = new SplitManager(this);
SignalStrengthTaskFactory strengthTaskFactory =
new SignalStrengthTaskFactory(ApiFeatures.getInstance());
signalManager =
new TaskExecuterManager(-1, strengthTaskFactory.create(this), this);
sensorManager = SensorManagerFactory.getSensorManager(this);
prefManager = new PreferenceManager(this);
registerLocationListener();
/**
/*
* After 5 min, check every minute that location listener still is
* registered and spit out additional debugging info to the logs:
*/
@@ -710,17 +724,27 @@ public class TrackRecordingService extends Service implements LocationListener {
showNotification();
prefManager.shutdown();
prefManager = null;
binder = null;
checkLocationListener.cancel();
checkLocationListener = null;
timer.cancel();
timer.purge();
unregisterLocationListener();
shutdownAnnouncer();
signalManager.shutdown();
signalManager = null;
splitManager.shutdown();
splitManager = null;
if (sensorManager != null) {
sensorManager.onDestroy();
sensorManager = null;
}
// Make sure we have no indirect references to this service.
locationManager = null;
notificationManager = null;
providerUtils = null;
binder.detachFromService();
binder = null;
// This should be the last operation.
releaseWakeLock();
super.onDestroy();
@@ -841,15 +865,25 @@ public class TrackRecordingService extends Service implements LocationListener {
throw new IllegalStateException(
"Unable to insert waypoint marker while not recording!");
}
if (waypoint.getLocation() != null) {
waypoint.setLength(length);
waypoint.setDuration(waypoint.getLocation().getTime()
- statsBuilder.getStatistics().getStartTime());
Uri uri = providerUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
if (waypoint.getLocation() == null) {
if (lastValidLocation == null) {
Log.w(MyTracksConstants.TAG, "Cannot insert waypoint with no location");
return -1;
}
waypoint.setLocation(lastValidLocation);
}
return -1;
if (waypoint.getTrackId() < 0) {
waypoint.setTrackId(recordingTrackId);
}
waypoint.setLength(length);
waypoint.setDuration(waypoint.getLocation().getTime()
- statsBuilder.getStatistics().getStartTime());
Uri uri = providerUtils.insertWaypoint(waypoint);
return Long.parseLong(uri.getLastPathSegment());
}
/**
@@ -896,82 +930,153 @@ public class TrackRecordingService extends Service implements LocationListener {
updateCurrentWaypoint();
return Long.parseLong(uri.getLastPathSegment());
}
private ServiceBinder binder = new ServiceBinder(this);
/**
* The ITrackRecordingService is defined through IDL.
* TODO: There is a bug in Android that leaks Binder instances. This bug is
* especially visible if we have a non-static class, as there is no way to
* nullify reference to the outer class (the service).
* A workaround is to use a static class and explicitly clear service
* and detach it from the underlying Binder. With this approach, we minimize
* the leak to 24 bytes per each service instance.
*
* For more details, see the following bug:
* http://code.google.com/p/android/issues/detail?id=6426.
*/
private ITrackRecordingService.Stub binder =
new ITrackRecordingService.Stub() {
@Override
public boolean isRecording() {
return TrackRecordingService.this.isRecording();
}
private static class ServiceBinder extends ITrackRecordingService.Stub {
private TrackRecordingService service;
public ServiceBinder(TrackRecordingService service) {
this.service = service;
}
/**
* Clears the reference to the outer class to minimize the leak.
*/
public void detachFromService() {
this.service = null;
attachInterface(null, null);
}
@Override
public boolean isRecording() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.isRecording();
}
@Override
public long getRecordingTrackId() {
return recordingTrackId;
}
@Override
public long getRecordingTrackId() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.recordingTrackId;
}
@Override
public boolean hasRecorded() {
return providerUtils.getLastTrackId() >= 0;
}
@Override
public boolean hasRecorded() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.providerUtils.getLastTrackId() >= 0;
}
@Override
public long startNewTrack() {
return TrackRecordingService.this.startNewTrack();
}
@Override
public long startNewTrack() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.startNewTrack();
}
/**
* Insert the given waypoint marker. Users can insert waypoint markers
* to tag locations with a name, description, category etc.
*
* @param waypoint a waypoint
* @return the unique id of the inserted marker
*/
@Override
public long insertWaypointMarker(Waypoint waypoint) {
return TrackRecordingService.this.insertWaypointMarker(waypoint);
}
/**
* Insert the given waypoint marker. Users can insert waypoint markers
* to tag locations with a name, description, category etc.
*
* @param waypoint a waypoint
* @return the unique id of the inserted marker
*/
@Override
public long insertWaypointMarker(Waypoint waypoint) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.insertWaypointMarker(waypoint);
}
/**
* Insert a statistics marker. A statistics marker holds the stats for
* the last segment up to this marker.
*
* @param location the location where to insert
* @return the unique id of the inserted marker
*/
@Override
public long insertStatisticsMarker(Location location) {
return TrackRecordingService.this.insertStatisticsMarker(location);
}
/**
* Insert a statistics marker. A statistics marker holds the stats for
* the last segment up to this marker.
*
* @param location the location where to insert
* @return the unique id of the inserted marker
*/
@Override
public long insertStatisticsMarker(Location location) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
return service.insertStatisticsMarker(location);
}
@Override
public void endCurrentTrack() {
TrackRecordingService.this.endCurrentTrack();
}
@Override
public void endCurrentTrack() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
service.endCurrentTrack();
}
@Override
public void deleteAllTracks() {
if (isRecording()) {
throw new IllegalStateException(
"Cannot delete all tracks while recording!");
}
providerUtils.deleteAllTracks();
}
@Override
public void deleteAllTracks() {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
if (isRecording()) {
throw new IllegalStateException("Cannot delete all tracks while recording!");
}
service.providerUtils.deleteAllTracks();
}
@Override
public void recordLocation(Location loc) {
onLocationChanged(loc);
}
};
@Override
public void recordLocation(Location loc) {
if (service == null) {
throw new IllegalStateException("The service has been already detached!");
}
service.onLocationChanged(loc);
}
@Override
public byte[] getSensorData() {
if (service.sensorManager == null) {
Log.d(MyTracksConstants.TAG, "No sensor manager for data.");
return null;
}
if (service.sensorManager.getSensorDataSet() == null) {
Log.d(MyTracksConstants.TAG, "Sensor data set is null.");
return null;
}
return service.sensorManager.getSensorDataSet().toByteArray();
}
@Override
public int getSensorState() {
if (service.sensorManager == null) {
Log.d(MyTracksConstants.TAG, "No sensor manager for data.");
return Sensor.SensorState.NONE.getNumber();
}
return service.sensorManager.getSensorState().getNumber();
}
}
public long startNewTrack() {
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
if (recordingTrackId != -1 || isRecording) {
throw new IllegalStateException("A track is already in progress!");
}
long startTime = System.currentTimeMillis();
acquireWakeLock();
@@ -996,14 +1101,20 @@ public class TrackRecordingService extends Service implements LocationListener {
showNotification();
registerLocationListener();
splitManager.restore();
signalManager.restore();
if (sensorManager != null) {
sensorManager.onStartTrack();
}
// Reset the number of auto-resume retries.
setAutoResumeTrackRetries(
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0), 0);
// Persist the current recording track.
prefManager.setRecordingTrack(recordingTrackId);
// Notify the world that we're now recording.
sendTrackBroadcast(
R.string.track_started_broadcast_action, recordingTrackId);
return recordingTrackId;
}
@@ -1033,8 +1144,27 @@ public class TrackRecordingService extends Service implements LocationListener {
"_id=" + recordingTrack.getId(), null);
}
showNotification();
long recordedTrackId = recordingTrackId;
prefManager.setRecordingTrack(recordingTrackId = -1);
if (sensorManager != null) {
sensorManager.shutdown();
}
releaseWakeLock();
// Notify the world that we're no longer recording.
sendTrackBroadcast(
R.string.track_stopped_broadcast_action, recordedTrackId);
}
private void sendTrackBroadcast(int actionResId, long trackId) {
Intent broadcastIntent =
new Intent()
.setAction(getString(actionResId))
.putExtra(getString(R.string.track_id_broadcast_extra), trackId);
sendBroadcast(broadcastIntent,
getString(R.string.broadcast_notifications_permission));
}
public TripStatistics getTripStatistics() {
@@ -1110,8 +1240,4 @@ public class TrackRecordingService extends Service implements LocationListener {
public SplitManager getSplitManager() {
return splitManager;
}
public TaskExecuterManager getSignalManager() {
return signalManager;
}
}
@@ -0,0 +1,432 @@
/*
* Copyright (C) 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* This class does all the work for setting up and managing Bluetooth
* connections with other devices. It has a thread that listens for incoming
* connections, a thread for connecting with a device, and a thread for
* performing data transmissions when connected.
*
* @author Sandor Dornbush
*/
public class BluetoothConnectionManager {
// Unique UUID for this application
private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
private MessageParser parser;
// Member fields
private final BluetoothAdapter adapter;
private final Handler handler;
private ConnectThread connectThread;
private ConnectedThread connectedThread;
private Sensor.SensorState state;
// Message types sent from the BluetoothSenorService Handler
public static final int MESSAGE_STATE_CHANGE = 1;
public static final int MESSAGE_READ = 2;
public static final int MESSAGE_WRITE = 3;
public static final int MESSAGE_DEVICE_NAME = 4;
// Key names received from the BluetoothSenorService Handler
public static final String DEVICE_NAME = "device_name";
/**
* Constructor. Prepares a new BluetoothSensor session.
*
* @param context The UI Activity Context
* @param handler A Handler to send messages back to the UI Activity
*/
public BluetoothConnectionManager(Handler handler, MessageParser parser) {
this.adapter = BluetoothAdapter.getDefaultAdapter();
this.state = Sensor.SensorState.NONE;
this.handler = handler;
this.parser = parser;
}
/**
* Set the current state of the sensor connection
*
* @param state An integer defining the current connection state
*/
private synchronized void setState(Sensor.SensorState state) {
// TODO pretty print this.
Log.d(MyTracksConstants.TAG, "setState(" + state + ")");
this.state = state;
// Give the new state to the Handler so the UI Activity can update
handler.obtainMessage(MESSAGE_STATE_CHANGE, state.getNumber(), -1).sendToTarget();
}
/**
* Return the current connection state.
*/
public synchronized Sensor.SensorState getState() {
return state;
}
/**
* Start the sensor service. Specifically start AcceptThread to begin a session
* in listening (server) mode. Called by the Activity onResume()
*/
public synchronized void start() {
Log.d(MyTracksConstants.TAG, "BluetoothConnectionManager.start()");
// Cancel any thread attempting to make a connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
setState(Sensor.SensorState.NONE);
}
/**
* Start the ConnectThread to initiate a connection to a remote device.
*
* @param device The BluetoothDevice to connect
*/
public synchronized void connect(BluetoothDevice device) {
Log.d(MyTracksConstants.TAG, "connect to: " + device);
// Cancel any thread attempting to make a connection
if (state == Sensor.SensorState.CONNECTING) {
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
// Start the thread to connect with the given device
connectThread = new ConnectThread(device);
connectThread.start();
setState(Sensor.SensorState.CONNECTING);
}
/**
* Start the ConnectedThread to begin managing a Bluetooth connection
*
* @param socket The BluetoothSocket on which the connection was made
* @param device The BluetoothDevice that has been connected
*/
public synchronized void connected(BluetoothSocket socket,
BluetoothDevice device) {
Log.d(MyTracksConstants.TAG, "connected");
// Cancel the thread that completed the connection
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
// Cancel any thread currently running a connection
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
// Start the thread to manage the connection and perform transmissions
connectedThread = new ConnectedThread(socket);
connectedThread.start();
// Send the name of the connected device back to the UI Activity
Message msg = handler.obtainMessage(MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(DEVICE_NAME, device.getName());
msg.setData(bundle);
handler.sendMessage(msg);
setState(Sensor.SensorState.CONNECTED);
}
/**
* Stop all threads
*/
public synchronized void stop() {
Log.d(MyTracksConstants.TAG, "stop()");
if (connectThread != null) {
connectThread.cancel();
connectThread = null;
}
if (connectedThread != null) {
connectedThread.cancel();
connectedThread = null;
}
setState(Sensor.SensorState.NONE);
}
/**
* Write to the ConnectedThread in an unsynchronized manner
*
* @param out The bytes to write
* @see ConnectedThread#write(byte[])
*/
public void write(byte[] out) {
// Create temporary object
ConnectedThread r;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
if (state != Sensor.SensorState.CONNECTED) {
return;
}
r = connectedThread;
}
// Perform the write unsynchronized
r.write(out);
}
/**
* Indicate that the connection attempt failed and notify the UI Activity.
*/
private void connectionFailed() {
setState(Sensor.SensorState.DISCONNECTED);
Log.i(MyTracksConstants.TAG, "Bluetooth connection failed.");
}
/**
* Indicate that the connection was lost and notify the UI Activity.
*/
private void connectionLost() {
setState(Sensor.SensorState.DISCONNECTED);
Log.i(MyTracksConstants.TAG, "Bluetooth connection lost.");
}
/**
* This thread runs while attempting to make an outgoing connection with a
* device. It runs straight through; the connection either succeeds or fails.
*/
private class ConnectThread extends Thread {
private final BluetoothSocket socket;
private final BluetoothDevice device;
public ConnectThread(BluetoothDevice device) {
setName("ConnectThread-" + device.getName());
this.device = device;
BluetoothSocket tmp = null;
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
tmp = getSocket();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "create() failed", e);
}
socket = tmp;
}
private BluetoothSocket getSocket() throws IOException {
try {
Class<? extends BluetoothDevice> c = device.getClass();
Method insecure = c.getMethod("createInsecureRfcommSocket",
Integer.class);
insecure.setAccessible(true);
return (BluetoothSocket) insecure.invoke(device, 1);
} catch (SecurityException e) {
Log.e(MyTracksConstants.TAG, "Unable to get insecure connect.", e);
} catch (NoSuchMethodException e) {
Log.e(MyTracksConstants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalArgumentException e) {
Log.e(MyTracksConstants.TAG, "Unable to get insecure connect.", e);
} catch (IllegalAccessException e) {
Log.e(MyTracksConstants.TAG, "Unable to get insecure connect.", e);
} catch (InvocationTargetException e) {
Log.e(MyTracksConstants.TAG, "Unable to get insecure connect.", e);
}
return device.createRfcommSocketToServiceRecord(SPP_UUID);
}
@Override
public void run() {
Log.d(MyTracksConstants.TAG, "BEGIN mConnectThread");
// Always cancel discovery because it will slow down a connection
adapter.cancelDiscovery();
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket.connect();
} catch (IOException e) {
connectionFailed();
// Close the socket
try {
socket.close();
} catch (IOException e2) {
Log.e(MyTracksConstants.TAG,
"unable to close() socket during connection failure", e2);
}
// Start the service over to restart listening mode
BluetoothConnectionManager.this.start();
return;
}
// Reset the ConnectThread because we're done
synchronized (BluetoothConnectionManager.this) {
connectThread = null;
}
// Start the connected thread
connected(socket, device);
}
public void cancel() {
try {
socket.close();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "close() of connect socket failed", e);
}
}
}
/**
* This thread runs during a connection with a remote device. It handles all
* incoming and outgoing transmissions.
*/
private class ConnectedThread extends Thread {
private final BluetoothSocket btSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
Log.d(MyTracksConstants.TAG, "create ConnectedThread");
btSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
@Override
public void run() {
Log.i(MyTracksConstants.TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[parser.getFrameSize()];
int bytes;
int offset = 0;
// Keep listening to the InputStream while connected
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer, offset, parser.getFrameSize() - offset);
if (bytes < 0) {
throw new IOException("EOF reached");
}
offset += bytes;
if (offset != parser.getFrameSize()) {
// partial frame received, call read() again to receive the rest
continue;
}
// check if its a valid frame
if (!parser.isValid(buffer)) {
int index = parser.findNextAlignment(buffer);
if (index > 0) {
// re-align
offset = parser.getFrameSize() - index;
System.arraycopy(buffer, index, buffer, 0, offset);
Log.w(MyTracksConstants.TAG, "Misaligned data, found new message at " +
index + " recovering...");
continue;
}
Log.w(MyTracksConstants.TAG, "Could not find valid data, dropping data");
offset = 0;
continue;
}
offset = 0;
// Send the obtained bytes to the UI Activity
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "disconnected", e);
connectionLost();
break;
}
}
}
/**
* Write to the connected OutStream.
*
* @param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
handler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "Exception during write", e);
}
}
public void cancel() {
try {
btSocket.close();
} catch (IOException e) {
Log.e(MyTracksConstants.TAG, "close() of connect socket failed", e);
}
}
}
}
@@ -0,0 +1,166 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
/**
* Manage the connection to a bluetooth sensor.
*
* @author Sandor Dornbush
*/
public class BluetoothSensorManager extends SensorManager {
// Local Bluetooth adapter
private BluetoothAdapter bluetoothAdapter = null;
// Member object for the sensor threads and connections.
private BluetoothConnectionManager connectionManager = null;
// Name of the connected device
private String connectedDeviceName = null;
private Context context = null;
private Sensor.SensorDataSet sensorDataSet = null;
private MessageParser parser;
public BluetoothSensorManager(
Context context, MessageParser parser) {
this.context = context;
this.parser = parser;
// Get local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// If BT is not available or not enabled quit.
if (!isEnabled()) {
return;
}
setupSensor();
}
private void setupSensor() {
Log.d(MyTracksConstants.TAG, "setupSensor()");
// Initialize the BluetoothSensorAdapter to perform bluetooth connections.
connectionManager = new BluetoothConnectionManager(messageHandler, parser);
}
public boolean isEnabled() {
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
public void setupChannel() {
if (!isEnabled() || connectionManager == null) {
Log.w(MyTracksConstants.TAG, "Disabled manager onStartTrack");
return;
}
SharedPreferences prefs =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
String address =
prefs.getString(context.getString(R.string.bluetooth_sensor_key), null);
if (address == null) {
return;
}
Log.w(MyTracksConstants.TAG, "Connecting to bluetooth sensor: " + address);
// Get the BluetoothDevice object
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device
connectionManager.connect(device);
// Performing this check in onResume() covers the case in which BT was
// not enabled during onStart(), so we were paused to enable it...
// onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
if (connectionManager != null) {
// Only if the state is STATE_NONE, do we know that we haven't started
// already
if (connectionManager.getState() == Sensor.SensorState.NONE) {
// Start the Bluetooth sensor services
Log.w(MyTracksConstants.TAG, "Disabled manager onStartTrack");
connectionManager.start();
}
}
}
public void onDestroy() {
// Stop the Bluetooth sensor services
if (connectionManager != null) {
connectionManager.stop();
}
}
public Sensor.SensorDataSet getSensorDataSet() {
return sensorDataSet;
}
public Sensor.SensorState getSensorState() {
return (connectionManager == null)
? Sensor.SensorState.NONE
: connectionManager.getState();
}
// The Handler that gets information back from the BluetoothSensorService
private final Handler messageHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothConnectionManager.MESSAGE_STATE_CHANGE:
// TODO should we update the SensorManager state var?
Log.i(MyTracksConstants.TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
break;
case BluetoothConnectionManager.MESSAGE_WRITE:
break;
case BluetoothConnectionManager.MESSAGE_READ:
byte[] readBuf = null;
try {
readBuf = (byte[]) msg.obj;
sensorDataSet = parser.parseBuffer(readBuf);
Log.d(MyTracksConstants.TAG, "MESSAGE_READ: " + sensorDataSet.toString());
} catch (IllegalArgumentException iae) {
sensorDataSet = null;
Log.i(MyTracksConstants.TAG,
"Got bad sensor data: " + new String(readBuf, 0, readBuf.length),
iae);
} catch (RuntimeException re) {
sensorDataSet = null;
Log.i(MyTracksConstants.TAG, "Unexpected exception on read.", re);
}
break;
case BluetoothConnectionManager.MESSAGE_DEVICE_NAME:
// save the connected device's name
connectedDeviceName =
msg.getData().getString(BluetoothConnectionManager.DEVICE_NAME);
Toast.makeText(context.getApplicationContext(),
"Connected to " + connectedDeviceName, Toast.LENGTH_SHORT)
.show();
break;
}
}
};
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
/**
* An interface for parsing a byte array to a SensorData object.
*
* @author Sandor Dornbush
*/
public interface MessageParser {
public int getFrameSize();
public Sensor.SensorDataSet parseBuffer(byte[] readBuff);
public boolean isValid(byte[] buffer);
public int findNextAlignment(byte[] buffer);
}
@@ -0,0 +1,134 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor;
/**
* Manage the connection to a sensor.
*
* @author Sandor Dornbush
*/
public abstract class SensorManager {
/**
* The maximum age where the data is considered valid.
*/
public static final long MAX_AGE = 5000;
/**
* Time to wait after a time out to retry.
*/
public static final int RETRY_PERIOD = 30000;
private Sensor.SensorState sensorState = Sensor.SensorState.NONE;
private long sensorStateTimestamp = 0;
/**
* A task to run periodically to check to see if connection was lost.
*/
private TimerTask checkSensorManager = new TimerTask() {
@Override
public void run() {
Log.i(MyTracksConstants.TAG,
"SensorManager state: " + getSensorState());
switch (getSensorState()) {
case CONNECTING:
long age = System.currentTimeMillis() - getSensorStateTimestamp();
if (age > 2 * RETRY_PERIOD) {
Log.i(MyTracksConstants.TAG, "Retrying connecting SensorManager.");
setupChannel();
}
break;
case DISCONNECTED:
Log.i(MyTracksConstants.TAG,
"Re-registering disconnected SensoManager.");
setupChannel();
break;
}
}
};
/**
* This timer invokes periodically the checkLocationListener timer task.
*/
private final Timer timer = new Timer();
/**
* Is the sensor that this manages enabled.
* @return true if the sensor is enabled
*/
public abstract boolean isEnabled();
/**
* This is called when my tracks starts recording a new track.
* This is the place to open connections to the sensor.
*/
public void onStartTrack() {
setupChannel();
timer.schedule(checkSensorManager, RETRY_PERIOD, RETRY_PERIOD);
}
public abstract void setupChannel();
public void shutdown() {
timer.cancel();
onDestroy();
}
/**
* This is called when my tracks stops recording.
* This is the place to shutdown any open connections.
*/
public abstract void onDestroy();
/**
* Return the last sensor reading.
* @return The last reading from the sensor.
*/
public abstract Sensor.SensorDataSet getSensorDataSet();
public void setSensorState(Sensor.SensorState sensorState) {
this.sensorState = sensorState;
}
/**
* Return the current sensor state.
* @return The current sensor state.
*/
public Sensor.SensorState getSensorState() {
return sensorState;
}
public long getSensorStateTimestamp() {
return sensorStateTimestamp;
}
/**
* @return True if the data is recent enough to be considered valid.
*/
public boolean isDataValid() {
return (System.currentTimeMillis() - getSensorDataSet().getCreationTime()) < MAX_AGE;
}
}
@@ -0,0 +1,61 @@
/*
* 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.services.sensors;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.MyTracksSettings;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
/**
* A factory of SensorManagers.
*
* @author Sandor Dornbush
*/
public class SensorManagerFactory {
private SensorManagerFactory() {
}
/**
* Get a new sensor manager.
* @param context Context to fetch system preferences.
* @return The sensor manager that corresponds to the sensor type setting.
*/
public static SensorManager getSensorManager(Context context) {
SharedPreferences prefs =
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
if (prefs == null) {
return null;
}
String sensor = prefs.getString(context.getString(R.string.sensor_type_key),
null);
Log.i(MyTracksConstants.TAG, "Creating sensor of type: " + sensor);
if (sensor == null) {
return null;
} else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) {
return new ZephyrSensorManager(context);
} else {
return null;
}
}
}
@@ -0,0 +1,61 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.maps.mytracks.R;
/**
* A collection of methods for message parsers.
*
* @author Sandor Dornbush
*/
public class SensorUtils {
private SensorUtils() {
}
/**
* Extract one unsigned short from a big endian byte array.
* @param buffer the buffer to extract the short from
* @param index the first byte to be interpreted as part of the short
* @return The unsigned short at the given index in the buffer
*/
public static int unsignedShortToInt(byte[] buffer, int index) {
int r = (buffer[index] & 0xFF) << 8;
r |= buffer[index + 1] & 0xFF;
return r;
}
public static String getStateAsString(Sensor.SensorState state, Context c) {
switch (state) {
case NONE:
return c.getString(R.string.none);
case CONNECTING:
return c.getString(R.string.connecting);
case CONNECTED:
return c.getString(R.string.connected);
case DISCONNECTED:
return c.getString(R.string.disconnected);
case SENDING:
return c.getString(R.string.sending);
default:
return "";
}
}
}
@@ -0,0 +1,76 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.util.Log;
import com.google.android.apps.mytracks.MyTracksConstants;
import com.google.android.apps.mytracks.content.Sensor;
/**
* An implementation of a SensorData parser for Zephyr HRM.
*
* @author Sandor Dornbush
*/
public class ZephyrMessageParser implements MessageParser {
@Override
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffer.length; i++) {
sb.append(String.format("%02X", buffer[i]));
}
Log.w(MyTracksConstants.TAG, "Got zephyr data: " + sb);
// The provided units are 1/16 strides per minute.
int cadence = SensorUtils.unsignedShortToInt(buffer, 56);
// Heart Rate
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(buffer[12] & 0xFF)
.setState(Sensor.SensorState.SENDING);
// Cadence
//.setCadence(cadence / 16)
//.build();
Sensor.SensorDataSet sds =
Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis())
.setHeartRate(b)
.build();
return sds;
}
@Override
public boolean isValid(byte[] buffer) {
// TODO crc etc.
return buffer[0] == 0x02 && buffer[59] == 0x03;
}
@Override
public int getFrameSize() {
return 60;
}
@Override
public int findNextAlignment(byte[] buffer) {
// TODO test or understand this code.
for (int i = 0; i < buffer.length - 1; i++) {
if (buffer[i] == 0x03 && buffer[i+1] == 0x02) {
return i;
}
}
return -1;
}
}
@@ -0,0 +1,26 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
public class ZephyrSensorManager extends BluetoothSensorManager {
public ZephyrSensorManager(Context context) {
super(context, new ZephyrMessageParser());
}
}
@@ -1,111 +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.stats;
/**
* A helper class that tracks a minimum and a maximum of a variable.
*
* @author Sandor Dornbush
*/
public class ExtremityMonitor {
/**
* The smallest value seen so far.
*/
private double min;
/**
* The largest value seen so far.
*/
private double max;
public ExtremityMonitor() {
reset();
}
/**
* Updates the min and the max with the new value.
*
* @param value the new value for the monitor
* @return true if an extremity was found
*/
public boolean update(double value) {
boolean changed = false;
if (value < min) {
min = value;
changed = true;
}
if (value > max) {
max = value;
changed = true;
}
return changed;
}
/**
* Gets the minimum value seen.
*
* @return The minimum value passed into the update() function
*/
public double getMin() {
return min;
}
/**
* Gets the maximum value seen.
*
* @return The maximum value passed into the update() function
*/
public double getMax() {
return max;
}
/**
* Resets this object to it's initial state where the min and max are unknown.
*/
public void reset() {
min = Double.POSITIVE_INFINITY;
max = Double.NEGATIVE_INFINITY;
}
/**
* Sets the minimum and maximum values.
*/
public void set(double min, double max) {
this.min = min;
this.max = max;
}
/**
* Sets the minimum value.
*/
public void setMin(double min) {
this.min = min;
}
/**
* Sets the maximum value.
*/
public void setMax(double max) {
this.max = max;
}
@Override
public String toString() {
return "Min: " + min + " Max: " + max;
}
}
@@ -1,2 +0,0 @@
package com.google.android.apps.mytracks.stats;
parcelable TripStatistics;
@@ -1,572 +0,0 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.stats;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Statistical data about a trip.
* The data in this class should be filled out by {@link TripStatisticsBuilder}.
*
* TODO: hashCode and equals
*
* @author Rodrigo Damazio
*/
public class TripStatistics implements Parcelable {
/**
* The start time for the trip. This is system time which might not match gps
* time.
*/
private long startTime = -1;
/**
* The stop time for the trip. This is the system time which might not match
* gps time.
*/
private long stopTime = -1;
/**
* The total time that we believe the user was traveling in milliseconds.
*/
private long movingTime;
/**
* The total time of the trip in milliseconds.
* This is only updated when new points are received, so it may be stale.
*/
private long totalTime;
/**
* The total distance in meters that the user traveled on this trip.
*/
private double totalDistance;
/**
* The total elevation gained on this trip in meters.
*/
private double totalElevationGain;
/**
* The maximum speed in meters/second reported that we believe to be a valid
* speed.
*/
private double maxSpeed;
/**
* The min and max latitude values seen in this trip.
*/
private final ExtremityMonitor latitudeExtremities = new ExtremityMonitor();
/**
* The min and max longitude values seen in this trip.
*/
private final ExtremityMonitor longitudeExtremities = new ExtremityMonitor();
/**
* The min and max elevation seen on this trip in meters.
*/
private final ExtremityMonitor elevationExtremities = new ExtremityMonitor();
/**
* The minimum and maximum grade calculations on this trip.
*/
private final ExtremityMonitor gradeExtremities = new ExtremityMonitor();
/**
* Default constructor.
*/
public TripStatistics() {
}
/**
* Copy constructor.
*
* @param other another statistics data object to copy from
*/
public TripStatistics(TripStatistics other) {
this.maxSpeed = other.maxSpeed;
this.movingTime = other.movingTime;
this.startTime = other.startTime;
this.stopTime = other.stopTime;
this.totalDistance = other.totalDistance;
this.totalElevationGain = other.totalElevationGain;
this.totalTime = other.totalTime;
this.latitudeExtremities.set(other.latitudeExtremities.getMin(),
other.latitudeExtremities.getMax());
this.longitudeExtremities.set(other.longitudeExtremities.getMin(),
other.longitudeExtremities.getMax());
this.elevationExtremities.set(other.elevationExtremities.getMin(),
other.elevationExtremities.getMax());
this.gradeExtremities.set(other.gradeExtremities.getMin(),
other.gradeExtremities.getMax());
}
/**
* Combines these statistics with those from another object.
* This assumes that the time periods covered by each do not intersect.
*
* @param other the other waypoint
*/
public void merge(TripStatistics other) {
startTime = Math.min(startTime, other.startTime);
stopTime = Math.max(stopTime, other.stopTime);
totalTime += other.totalTime;
movingTime += other.movingTime;
totalDistance += other.totalDistance;
totalElevationGain += other.totalElevationGain;
maxSpeed = Math.max(maxSpeed, other.maxSpeed);
latitudeExtremities.update(other.latitudeExtremities.getMax());
latitudeExtremities.update(other.latitudeExtremities.getMin());
longitudeExtremities.update(other.longitudeExtremities.getMax());
longitudeExtremities.update(other.longitudeExtremities.getMin());
elevationExtremities.update(other.elevationExtremities.getMax());
elevationExtremities.update(other.elevationExtremities.getMin());
gradeExtremities.update(other.gradeExtremities.getMax());
gradeExtremities.update(other.gradeExtremities.getMin());
}
/**
* Gets the time that this track started.
*
* @return The number of milliseconds since epoch to the time when this track
* started
*/
public long getStartTime() {
return startTime;
}
/**
* Gets the time that this track stopped.
*
* @return The number of milliseconds since epoch to the time when this track
* stopped
*/
public long getStopTime() {
return stopTime;
}
/**
* Gets the total time that this track has been active.
* This statistic is only updated when a new point is added to the statistics,
* so it may be off. If you need to calculate the proper total time, use
* {@link #getStartTime} with the current time.
*
* @return The total number of milliseconds the track was active
*/
public long getTotalTime() {
return totalTime;
}
/**
* Gets the total distance the user traveled.
*
* @return The total distance traveled in meters
*/
public double getTotalDistance() {
return totalDistance;
}
/**
* Gets the the average speed the user traveled.
* This calculation only takes into account the displacement until the last
* point that was accounted for in statistics.
*
* @return The average speed in m/s
*/
public double getAverageSpeed() {
return totalDistance / ((double) totalTime / 1000);
}
/**
* Gets the the average speed the user traveled when they were actively
* moving.
*
* @return The average moving speed in m/s
*/
public double getAverageMovingSpeed() {
return totalDistance / ((double) movingTime / 1000);
}
/**
* Gets the the maximum speed for this track.
*
* @return The maximum speed in m/s
*/
public double getMaxSpeed() {
return maxSpeed;
}
/**
* Gets the moving time.
*
* @return The total number of milliseconds the user was moving
*/
public long getMovingTime() {
return movingTime;
}
/**
* Gets the total elevation gain for this trip. This is calculated as the sum
* of all positive differences in the smoothed elevation.
*
* @return The elevation gain in meters for this trip
*/
public double getTotalElevationGain() {
return totalElevationGain;
}
/**
* Returns the leftmost position (lowest longitude) of the track, in signed
* decimal degrees.
*/
public int getLeft() {
return (int) (longitudeExtremities.getMin() * 1E6);
}
/**
* Returns the rightmost position (highest longitude) of the track, in signed
* decimal degrees.
*/
public int getRight() {
return (int) (longitudeExtremities.getMax() * 1E6);
}
/**
* Returns the bottommost position (lowest latitude) of the track, in meters.
*/
public int getBottom() {
return (int) (latitudeExtremities.getMin() * 1E6);
}
/**
* Returns the topmost position (highest latitude) of the track, in meters.
*/
public int getTop() {
return (int) (latitudeExtremities.getMax() * 1E6);
}
/**
* Gets the minimum elevation seen on this trip. This is calculated from the
* smoothed elevation so this can actually be more than the current elevation.
*
* @return The smallest elevation reading for this trip in meters
*/
public double getMinElevation() {
return elevationExtremities.getMin();
}
/**
* Gets the maximum elevation seen on this trip. This is calculated from the
* smoothed elevation so this can actually be less than the current elevation.
*
* @return The largest elevation reading for this trip in meters
*/
public double getMaxElevation() {
return elevationExtremities.getMax();
}
/**
* Gets the maximum grade for this trip.
*
* @return The maximum grade for this trip as a fraction
*/
public double getMaxGrade() {
return gradeExtremities.getMax();
}
/**
* Gets the minimum grade for this trip.
*
* @return The minimum grade for this trip as a fraction
*/
public double getMinGrade() {
return gradeExtremities.getMin();
}
// Setters - to be used when restoring state or loading from the DB
/**
* Sets the start time for this trip.
*
* @param startTime the start time, in milliseconds since the epoch
*/
public void setStartTime(long startTime) {
this.startTime = startTime;
}
/**
* Sets the stop time for this trip.
*
* @param stopTime the stop time, in milliseconds since the epoch
*/
public void setStopTime(long stopTime) {
this.stopTime = stopTime;
}
/**
* Sets the total moving time.
*
* @param movingTime the moving time in milliseconds
*/
public void setMovingTime(long movingTime) {
this.movingTime = movingTime;
}
/**
* Sets the total trip time.
*
* @param totalTime the total trip time in milliseconds
*/
public void setTotalTime(long totalTime) {
this.totalTime = totalTime;
}
/**
* Sets the total trip distance.
*
* @param totalDistance the trip distance in meters
*/
public void setTotalDistance(double totalDistance) {
this.totalDistance = totalDistance;
}
/**
* Sets the total elevation variation during the trip.
*
* @param totalElevationGain the elevation variation in meters
*/
public void setTotalElevationGain(double totalElevationGain) {
this.totalElevationGain = totalElevationGain;
}
/**
* Sets the maximum speed reached during the trip.
*
* @param maxSpeed the maximum speed in meters per second
*/
public void setMaxSpeed(double maxSpeed) {
this.maxSpeed = maxSpeed;
}
/**
* Sets the minimum elevation reached during the trip.
*
* @param elevation the minimum elevation in meters
*/
public void setMinElevation(double elevation) {
elevationExtremities.setMin(elevation);
}
/**
* Sets the maximum elevation reached during the trip.
*
* @param elevation the maximum elevation in meters
*/
public void setMaxElevation(double elevation) {
elevationExtremities.setMax(elevation);
}
/**
* Sets the minimum grade obtained during the trip.
*
* @param grade the grade as a fraction (-1.0 would mean vertical downwards)
*/
public void setMinGrade(double grade) {
gradeExtremities.setMin(grade);
}
/**
* Sets the maximum grade obtained during the trip).
*
* @param grade the grade as a fraction (1.0 would mean vertical upwards)
*/
public void setMaxGrade(double grade) {
gradeExtremities.setMax(grade);
}
/**
* Sets the bounding box for this trip.
* The unit for all parameters is signed decimal degrees (degrees * 1E6).
*
* @param leftE6 the westmost longitude reached
* @param topE6 the northmost latitude reached
* @param rightE6 the eastmost longitude reached
* @param bottomE6 the southmost latitude reached
*/
public void setBounds(int leftE6, int topE6, int rightE6, int bottomE6) {
latitudeExtremities.set(bottomE6 / 1E6, topE6 / 1E6);
longitudeExtremities.set(leftE6 / 1E6, rightE6 / 1E6);
}
// Data manipulation methods
/**
* Adds to the current total distance.
*
* @param distance the distance to add in meters
*/
void addTotalDistance(double distance) {
totalDistance += distance;
}
/**
* Adds to the total elevation variation.
*
* @param gain the elevation variation in meters
*/
void addTotalElevationGain(double gain) {
totalElevationGain += gain;
}
/**
* Adds to the total moving time of the trip.
*
* @param time the time in milliseconds
*/
void addMovingTime(long time) {
movingTime += time;
}
/**
* Accounts for a new latitude value for the bounding box.
*
* @param latitude the latitude value in signed decimal degrees
*/
void updateLatitudeExtremities(double latitude) {
latitudeExtremities.update(latitude);
}
/**
* Accounts for a new longitude value for the bounding box.
*
* @param longitude the longitude value in signed decimal degrees
*/
void updateLongitudeExtremities(double longitude) {
longitudeExtremities.update(longitude);
}
/**
* Accounts for a new elevation value for the bounding box.
*
* @param elevation the elevation value in meters
*/
void updateElevationExtremities(double elevation) {
elevationExtremities.update(elevation);
}
/**
* Accounts for a new grade value.
*
* @param grade the grade value as a fraction
*/
void updateGradeExtremities(double grade) {
gradeExtremities.update(grade);
}
// String conversion
@Override
public String toString() {
return "TripStatistics { Start Time: " + getStartTime()
+ "; Total Time: " + getTotalTime()
+ "; Moving Time: " + getMovingTime()
+ "; Total Distance: " + getTotalDistance()
+ "; Elevation Gain: " + getTotalElevationGain()
+ "; Min Elevation: " + getMinElevation()
+ "; Max Elevation: " + getMaxElevation()
+ "; Average Speed: " + getAverageMovingSpeed()
+ "; Min Grade: " + getMinGrade()
+ "; Max Grade: " + getMaxGrade()
+ "}";
}
// Parcelable interface and creator
/**
* Creator of statistics data from parcels.
*/
public static class Creator
implements Parcelable.Creator<TripStatistics> {
@Override
public TripStatistics createFromParcel(Parcel source) {
TripStatistics data = new TripStatistics();
data.startTime = source.readLong();
data.movingTime = source.readLong();
data.totalTime = source.readLong();
data.totalDistance = source.readDouble();
data.totalElevationGain = source.readDouble();
data.maxSpeed = source.readDouble();
double minLat = source.readDouble();
double maxLat = source.readDouble();
data.latitudeExtremities.set(minLat, maxLat);
double minLong = source.readDouble();
double maxLong = source.readDouble();
data.longitudeExtremities.set(minLong, maxLong);
double minElev = source.readDouble();
double maxElev = source.readDouble();
data.elevationExtremities.set(minElev, maxElev);
double minGrade = source.readDouble();
double maxGrade = source.readDouble();
data.gradeExtremities.set(minGrade, maxGrade);
return data;
}
@Override
public TripStatistics[] newArray(int size) {
return new TripStatistics[size];
}
}
/**
* Creator of {@link TripStatistics} from parcels.
*/
public static final Creator CREATOR = new Creator();
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(startTime);
dest.writeLong(movingTime);
dest.writeLong(totalTime);
dest.writeDouble(totalDistance);
dest.writeDouble(totalElevationGain);
dest.writeDouble(maxSpeed);
dest.writeDouble(latitudeExtremities.getMin());
dest.writeDouble(latitudeExtremities.getMax());
dest.writeDouble(longitudeExtremities.getMin());
dest.writeDouble(longitudeExtremities.getMax());
dest.writeDouble(elevationExtremities.getMin());
dest.writeDouble(elevationExtremities.getMax());
dest.writeDouble(gradeExtremities.getMin());
dest.writeDouble(gradeExtremities.getMax());
}
}
@@ -102,10 +102,6 @@ public class ApiFeatures {
return true;
}
public boolean hasModernSignalStrength() {
return getApiLevel() >= 7;
}
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
@@ -0,0 +1,200 @@
/*
* 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.
*/
package com.google.android.apps.mytracks.util;
import com.google.android.apps.mytracks.MyTracksConstants;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.os.Build;
import android.util.Log;
import java.util.List;
import java.util.Set;
/**
* Utilities for dealing with bluetooth devices.
* This can be used safely even in systems that don't support bluetooth,
* in which case a dummy implementation will be used.
*
* @author Rodrigo Damazio
*/
public abstract class BluetoothDeviceUtils {
public static final String ANY_DEVICE = "any";
private static BluetoothDeviceUtils instance;
/**
* Dummy implementation, for systems that don't support bluetooth.
*/
private static class DummyImpl extends BluetoothDeviceUtils {
@Override
public void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses) {
// Do nothing - no devices to add
}
@Override
public BluetoothDevice findDeviceMatching(String targetDeviceAddress) {
return null;
}
}
/**
* Real implementation, for systems that DO support bluetooth.
*/
private static class RealImpl extends BluetoothDeviceUtils {
private final BluetoothAdapter bluetoothAdapter;
public RealImpl() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
throw new IllegalStateException("Unable to get bluetooth adapter");
}
}
@Override
public void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses) {
ensureNotDiscovering();
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
BluetoothClass bluetoothClass = device.getBluetoothClass();
if (bluetoothClass != null) {
// Not really sure what we want, but I know what we don't want.
switch(bluetoothClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
case BluetoothClass.Device.Major.PHONE:
break;
default:
deviceAddresses.add(device.getAddress());
deviceNames.add(device.getName());
}
}
}
}
@Override
public BluetoothDevice findDeviceMatching(String targetDeviceAddress) {
if (targetDeviceAddress.equals(ANY_DEVICE)) {
return findAnyDevice();
} else {
return findDeviceByAddress(targetDeviceAddress);
}
}
/**
* Finds and returns the first suitable bluetooth sensor.
*/
private BluetoothDevice findAnyDevice() {
ensureNotDiscovering();
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
// Look for the first paired computer device
if (isSuitableDevice(device)) {
return device;
}
}
return null;
}
/**
* Finds and returns a device with the given address, or null if it's not
* a suitable sensor.
*/
private BluetoothDevice findDeviceByAddress(String targetDeviceAddress) {
ensureNotDiscovering();
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(targetDeviceAddress);
if (isSuitableDevice(device)) {
return device;
}
return null;
}
/**
* Ensures the bluetooth adapter is not in discovery mode.
*/
private void ensureNotDiscovering() {
// If it's in discovery mode, cancel that for now.
bluetoothAdapter.cancelDiscovery();
}
/**
* Checks whether the given device is a suitable sensor.
*
* @param device the device to check
* @return true if it's suitable, false otherwise
*/
private boolean isSuitableDevice(BluetoothDevice device) {
// Check that the device is bonded
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
return false;
}
return true;
}
}
/**
* Returns the proper (singleton) instance of this class.
*/
public static BluetoothDeviceUtils getInstance() {
if (instance == null) {
if (!isBluetoothMethodSupported()) {
Log.d(MyTracksConstants.TAG, "Using dummy bluetooth utils");
instance = new DummyImpl();
} else {
Log.d(MyTracksConstants.TAG, "Using real bluetooth utils");
try {
instance = new RealImpl();
} catch (IllegalStateException ise) {
Log.w(MyTracksConstants.TAG, "Oops, I mean, using dummy bluetooth utils", ise);
instance = new DummyImpl();
}
}
}
return instance;
}
/**
* Populates the given lists with the names and addresses of all suitable
* bluetooth devices.
*
* @param deviceNames the list to populate with user-visible names
* @param deviceAddresses the list to populate with device addresses
*/
public abstract void populateDeviceLists(List<String> deviceNames, List<String> deviceAddresses);
/**
* Finds the bluetooth device with the given address.
*
* @param targetDeviceAddress the address of the device, or
* {@link #ANY_DEVICE} for using the first suitable device
* @return the device's descriptor, or null if not found
*/
public abstract BluetoothDevice findDeviceMatching(String targetDeviceAddress);
/**
* @return whether the bluetooth method is supported on this device
*/
public static boolean isBluetoothMethodSupported() {
return Integer.parseInt(Build.VERSION.SDK) >= 5;
}
}