diff --git a/MyTracks/.classpath b/MyTracks/.classpath
index 7c5cb8f1e..3d5ddb422 100644
--- a/MyTracks/.classpath
+++ b/MyTracks/.classpath
@@ -6,5 +6,7 @@
+
+
diff --git a/MyTracks/.project b/MyTracks/.project
index 03e694b1f..ef19912e8 100644
--- a/MyTracks/.project
+++ b/MyTracks/.project
@@ -30,4 +30,11 @@
com.android.ide.eclipse.adt.AndroidNature
org.eclipse.jdt.core.javanature
+
+
+ MyTracksLib_src
+ 2
+ _android_MyTracksLib_e2802071/src
+
+
diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 2b6362942..578bd1e48 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -1,9 +1,24 @@
+
+ android:versionCode="22"
+ android:versionName="1.0.21">
@@ -13,7 +28,10 @@
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
+ 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" />
+ android:label="MyTracks"
+ android:exported="false" />
+ android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
+ android:exported="false" />
@@ -60,19 +80,26 @@
android:label="@string/aggregated_stats" />
+ android:label="@string/statistics"
+ android:exported="false" />
+
+
+ android:label="@string/elevation_label"
+ android:exported="false" />
+ android:label="@string/welcome"
+ android:exported="false" />
+ android:exported="true"
+ android:permission="com.google.android.apps.mytracks.WRITE_TRACK_DATA" />
@@ -89,6 +116,8 @@
+
+
@@ -102,7 +131,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MyTracks/default.properties b/MyTracks/default.properties
index 85cbfc4dd..fcf640319 100644
--- a/MyTracks/default.properties
+++ b/MyTracks/default.properties
@@ -11,3 +11,4 @@
split.density=false
# Project target.
target=Google Inc.:Google APIs:9
+android.library.reference.1=../MyTracksLib/
diff --git a/MyTracks/proguard.cfg b/MyTracks/proguard.cfg
new file mode 100644
index 000000000..8ad7d3352
--- /dev/null
+++ b/MyTracks/proguard.cfg
@@ -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 ;
+}
+
+-keepclasseswithmembernames class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+ public (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 *;
+}
diff --git a/MyTracks/res/drawable/chart_settings.png b/MyTracks/res/drawable/chart_settings.png
index d608eedf3..63147af45 100644
Binary files a/MyTracks/res/drawable/chart_settings.png and b/MyTracks/res/drawable/chart_settings.png differ
diff --git a/MyTracks/res/drawable/menu_sensor_status.png b/MyTracks/res/drawable/menu_sensor_status.png
new file mode 100644
index 000000000..80e68b847
Binary files /dev/null and b/MyTracks/res/drawable/menu_sensor_status.png differ
diff --git a/MyTracks/res/layout/chart_settings.xml b/MyTracks/res/layout/chart_settings.xml
index e2f13303d..6178579bc 100644
--- a/MyTracks/res/layout/chart_settings.xml
+++ b/MyTracks/res/layout/chart_settings.xml
@@ -103,6 +103,21 @@ limitations under the License.
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/speed"/>
+
+
+
diff --git a/MyTracks/res/layout/sensor_state.xml b/MyTracks/res/layout/sensor_state.xml
new file mode 100644
index 000000000..a6cd7f810
--- /dev/null
+++ b/MyTracks/res/layout/sensor_state.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_column="5"
+ android:text="@string/watts" />
+
+
+
+
+
+
+
+
+
+ android:layout_column="2"
+ android:text="@string/revolutions_per_minute" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MyTracks/res/layout/stats.xml b/MyTracks/res/layout/stats.xml
index 342e3b59f..af30d0875 100644
--- a/MyTracks/res/layout/stats.xml
+++ b/MyTracks/res/layout/stats.xml
@@ -42,15 +42,10 @@ limitations under the License.
+ android:layout_span="4"/>
+ style="@style/StatsUnit" />
+ style="@style/StatsDivider" />
+ style="@style/StatsUnit" />
@@ -100,43 +83,25 @@ limitations under the License.
+ android:layout_column="1" />
+ style="@style/StatsUnitSolo" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister"
+ android:layout_column="4" />
+ style="@style/StatsUnit" />
+ style="@style/StatsDivider" />
+ style="@style/StatsUnit" />
@@ -182,33 +135,19 @@ limitations under the License.
+ style="@style/StatsRegister" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister" />
+ style="@style/StatsUnit" />
+ style="@style/StatsDivider" />
+ style="@style/StatsUnit" />
@@ -254,33 +181,18 @@ limitations under the License.
+ style="@style/StatsRegister" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister" />
+ style="@style/StatsUnit" />
android:layout_height="3dip"
android:layout_width="fill_parent" />
+ style="@style/StatsUnit" />
@@ -326,46 +228,24 @@ limitations under the License.
+ style="@style/StatsRegister" />
+ style="@style/StatsUnitSolo" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister" />
+ style="@style/StatsUnitSolo" />
@@ -384,46 +264,24 @@ limitations under the License.
+ style="@style/StatsRegister" />
+ style="@style/StatsUnitSolo" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister" />
+ style="@style/StatsUnitSolo" />
@@ -442,36 +300,18 @@ limitations under the License.
+ style="@style/StatsRegister" />
+ style="@style/StatsSpacer"
+ android:layout_column="3"/>
+ style="@style/StatsRegister" />
@@ -488,36 +328,19 @@ limitations under the License.
+ style="@style/StatsRegister.Small" />
+ style="@style/StatsRegister.Small" />
diff --git a/MyTracks/res/menu/main.xml b/MyTracks/res/menu/main.xml
index 9acffc9b3..8ff5a9e71 100644
--- a/MyTracks/res/menu/main.xml
+++ b/MyTracks/res/menu/main.xml
@@ -22,10 +22,14 @@
android:title="@string/settings"/>
+
+
- Beim teilen mit Freunden wird nur die Karten URL gesendet.
Beim teilen mit Freunden wird der GPS Track selbst gesendet.
- Empfangsstärke Aufzeichnungsfrequenz
- Dauer zwischen zwei Aufzeichnungen der Telefonempfangsstärke.
-
Zerteilungs Frequenz
Längste Dauer oder Entfernung nach der automatisch ein Marker eingefügt wird.
Tempo
diff --git a/MyTracks/res/values-es/strings.xml b/MyTracks/res/values-es/strings.xml
index 5e6a29f8b..7a58e15e8 100644
--- a/MyTracks/res/values-es/strings.xml
+++ b/MyTracks/res/values-es/strings.xml
@@ -187,9 +187,6 @@ limitations under the License.
Sólo el URL del mapa será enviado al compartir
Los contenidos de la pista también serán incluídos al compartir
- Frecuencia de medición de señal
- Tiempo entre grabaciones de la intensidad de la señal del teléfono
-
Dividir frecuencia
Tiempo o distancia máxima para insertar una nueva marca de división
Paso
diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml
index a67f90749..bbe44c007 100644
--- a/MyTracks/res/values-fr/strings.xml
+++ b/MyTracks/res/values-fr/strings.xml
@@ -187,9 +187,6 @@ limitations under the License.
Seule l\'adresse URL de la carte sera envoyée lors du partage
Le contenu du parcours sera également inclus lors du partage
- Échantillonnage du signal
- Intervalle de temps entre deux enregistrements de la force du signal
-
Fréquence de séparation
Temps ou distance minimum avant l\'insertion d\'un marqueur de séparation
Rythme
diff --git a/MyTracks/res/values-it/strings.xml b/MyTracks/res/values-it/strings.xml
index 2f9024552..2fe4ff4ba 100644
--- a/MyTracks/res/values-it/strings.xml
+++ b/MyTracks/res/values-it/strings.xml
@@ -193,9 +193,6 @@ limitations under the License.
Solo l\'URL della mappa sarà inviata quando condividi un percorso
Anche il contenuto del percorso sarà inviato quando condividi un persorso
- Frequenza di campionamento
- Tempo fra due registrazioni della qualità del segnale cellulare
-
Frequenza di divisione
Tempo o distanza massima dopo di cui il percorso verrà diviso
Andatura
diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml
index b88304819..6a5745f77 100644
--- a/MyTracks/res/values-ja/strings.xml
+++ b/MyTracks/res/values-ja/strings.xml
@@ -194,9 +194,6 @@ limitations under the License.
共有時にマップのURLのみ送信します
共有時にトラックのコンテントが含まれます
- 信号サンプリング周波数
- 端末の信号強度記録の時間間隔
-
分割頻度
分割マーカを挿入する時間または距離の間隔
ペース
@@ -384,12 +381,4 @@ limitations under the License.
計測単位についてのご注意
デフォルトの単位はメートルです.設定を変更しますか?
-
-
- 誤り率: %1$d
- S/N比: %1$d
- Ec/Io比: %1$.1f dB
- GSM 強度: %1$d
- CDMA 強度: %1$d dBm
- EVDO 強度: %1$d dBm
diff --git a/MyTracks/res/values-pl/strings.xml b/MyTracks/res/values-pl/strings.xml
index 049209c7d..2d597a9e2 100644
--- a/MyTracks/res/values-pl/strings.xml
+++ b/MyTracks/res/values-pl/strings.xml
@@ -195,9 +195,6 @@ limitations under the License.
Tylko odnośnik do mapy będzie wysyłany podczas dzielenia się
Zawartość trasy będzie również przesyłana
- Częstotliowść próbkowania sygnału
- Czas między dwoma zapisami siły sygnału telefonu
-
Częstotliwość rozdzielania
Maksymalny czas lub odległość po którym wstawiony zostanie znacznik rozdzielenia
@@ -416,12 +413,4 @@ limitations under the License.
Jednostki miary
Domyślnie używane są jednostki metryczne. Chcesz to zmienić?
-
-
- Poziom błędów: %1$d
- Stosunek sygnału do szumu: %1$d
- Ec/lo: %1$.1f dB
- Siła GSM: %1$d
- Siła CDMA: %1$d dBm
- Siła EVDO: %1$d dBm
diff --git a/MyTracks/res/values-sv/strings.xml b/MyTracks/res/values-sv/strings.xml
index fa43e18e7..3b18a5f95 100644
--- a/MyTracks/res/values-sv/strings.xml
+++ b/MyTracks/res/values-sv/strings.xml
@@ -195,9 +195,6 @@ limitations under the License.
Bara länken till kartan kommer att skickas vid delning
Innehållet i spåret kommer också att inkluderas vid delning
- Signalprovsfrekvens
- Tid mellan två inspelningar av telefonens signalstyrka
-
Delningsfrekvens
Maximala tiden eller distansen varefter en ny delningsmarkör kommer infogas
Tempo
@@ -383,12 +380,4 @@ limitations under the License.
Mätenheter
Som standard är enheterna metriska. Vill du ändra den inställningen?
-
-
- Felfrekvens: %1$d
- Signal / brus-förhållande: %1$d
- Ec/lo: %1$.1f dB
- GSM-styrka: %1$d
- CDMA-styrka: %1$d dBm
- EVDO-styrka: %1$d dBm
diff --git a/MyTracks/res/values-zh-rCN/strings.xml b/MyTracks/res/values-zh-rCN/strings.xml
index 648550a39..cacf8422c 100644
--- a/MyTracks/res/values-zh-rCN/strings.xml
+++ b/MyTracks/res/values-zh-rCN/strings.xml
@@ -201,9 +201,6 @@ limitations under the License.
在分享时只发送地图的 URL
在分享时也包括轨迹的内容
- 信号采样频率
- 记录电话信号强度的时间间隔
-
切割频度
插入新切割标记所需要达到的最大时间或距离
@@ -451,14 +448,6 @@ limitations under the License.
测量单位提示
默认单位均为公制. 你想要修改此设置吗?
-
- 错误率: %1$d
- 信噪比: %1$d
- Ec/lo: %1$.1f dB
- GSM 强度 %1$d
- CDMA 强度 %1$d dBm
- EVDO 强度 %1$d dBm
-
蓝牙
蓝牙设置
@@ -467,12 +456,6 @@ limitations under the License.
感应器选项
-
-
-
-
-
-
连接到 %s
蓝牙感应器连接中 ...
diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml
index ac7772419..3eb41e8fe 100644
--- a/MyTracks/res/values/arrays.xml
+++ b/MyTracks/res/values/arrays.xml
@@ -390,4 +390,16 @@ limitations under the License.
- CSV
- TCX
+
+
+ - None
+ - Zephyr Bluetooth
+
+
+
+
+ - @string/none
+ - @string/zephyr_sensor_type
+
+
diff --git a/MyTracks/res/values/colors.xml b/MyTracks/res/values/colors.xml
index 911148cc0..3ce80b119 100644
--- a/MyTracks/res/values/colors.xml
+++ b/MyTracks/res/values/colors.xml
@@ -15,17 +15,22 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
- #FFffd700
- #FFFFFFFF
#FF000000
- #FF00FFFF
#FF0000FF
#400000FF
- #FFFF0000
- #FF00AA00
- #FFAAAAAA
#FF444444
+ #FFffd700
+ #FFAAAAAA
+ #FF00AA00
+ #FF00FFFF
#70000000
+ #FF401465
+ #40401465
+ #FFFF0000
+ #40FF0000
#FFF0F0F0
#FF0F0F0F
+ #FFFFFFFF
+ #FFF8FF00
+ #40F8FF00
diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml
index 43a843ebc..ebf7cdf07 100644
--- a/MyTracks/res/values/keys.xml
+++ b/MyTracks/res/values/keys.xml
@@ -33,10 +33,8 @@ limitations under the License.
sendStatsAndPoints
sendToDocs
sendToMyMaps
- sensorId
sensorType
shareUrlOnly
- signalSamplingFrequency
splitFrequency
timestampTrackName
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 6212f9cf2..b837dcc82 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -32,6 +32,7 @@ limitations under the License.
Please select the track to view
Current track
Last segment
+ Sensor State
Aggregated Statistics
@@ -201,9 +202,6 @@ limitations under the License.
Only the URL of the map will be sent when sharing
The contents of the track will also be included when sharing
- Signal sampling frequency
- Time between two recordings of the phone\'s signal strength
-
Split frequency
Maximum time or distance after which a new split marker will be inserted
@@ -447,14 +445,6 @@ limitations under the License.
Units of Measure Notice
By default units are metric. Would you like to change that setting?
-
- Error Rate: %1$d
- Signal to Noise Ratio: %1$d
- Ec/lo: %1$.1f dB
- GSM Strength: %1$d
- CDMA Strength: %1$d dBm
- EVDO Strength: %1$d dBm
-
Bluetooth
Bluetooth options
@@ -465,9 +455,7 @@ limitations under the License.
NONE
- ANT
- SRM
- ATHLOSOFT
+ ZEPHYR
Connected to %s
@@ -478,5 +466,20 @@ limitations under the License.
Sensor Data Time
Power meter enabled
Heart rate monitor enabled
+ No Sensor Manager
+ Enabled
+ Connecting
+ Connected
+ Disconnected
+ No Sensor Service
+ Searching
+ Sending
+
+ receive track recording notifications
+ read recorded track data
+ write recorded track data, start/stop recording tracks
+ Be notified when a track starts or stops being recorded.
+ Be able to read track data, such as points and waypoints.
+ Be able to write track data, such as starting recording or adding waypoints.
diff --git a/MyTracks/res/values/styles.xml b/MyTracks/res/values/styles.xml
new file mode 100644
index 000000000..b9e0bb68f
--- /dev/null
+++ b/MyTracks/res/values/styles.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml
index 848bab5a2..c0b519386 100644
--- a/MyTracks/res/xml/preferences.xml
+++ b/MyTracks/res/xml/preferences.xml
@@ -84,14 +84,6 @@ limitations under the License.
android:entryValues="@array/min_required_accuracy_values"
android:defaultValue="200" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
data = new ArrayList();
// 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 theData = new ArrayList();
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);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java b/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
index 0610e0671..34d64e82f 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartSettingsDialog.java
@@ -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();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
index d5ff24834..acf61ab16 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartValueSeries.java
@@ -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();
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
index 4ff195a76..06cc848bf 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/ChartView.java
@@ -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.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java
index 374e4d945..aecda4b3d 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MenuManager.java
@@ -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;
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
index 87bf63159..60ff3a840 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java
@@ -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.
*/
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
index e58c99388..c47365ae1 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
@@ -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 entries = new ArrayList();
+ List entryValues = new ArrayList();
+
+ // 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);
+ }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
new file mode 100644
index 000000000..fb9559d26
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/SensorStateActivity.java
@@ -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));
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java
index 010e553f8..89604e4b2 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProvider.java
@@ -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);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java b/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
index 571859cc9..e1d58b354 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/GpxImporter.java
@@ -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);
+ }
}
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
index 7ad325f41..a09e2fd60 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/TcxTrackWriter.java
@@ -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("");
+ 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(" ");
+ pw.print("");
+ pw.print(sensorData.getHeartRate().getValue());
+ pw.print("");
+ pw.println("");
+ }
+ if (sensorData.hasPower()
+ && sensorData.getPower().getState() == Sensor.SensorState.SENDING
+ && sensorData.getPower().hasValue()) {
+ pw.print(" ");
+ pw.print("");
+ pw.print("");
+ pw.print(sensorData.getPower().getValue());
+ pw.print("");
+ pw.println("");
+ }
+ if (sensorData.hasCadence()
+ && sensorData.getCadence().getState() == Sensor.SensorState.SENDING
+ && sensorData.getCadence().hasValue()) {
+ pw.print(" ");
+ pw.print(sensorData.getCadence().getValue());
+ pw.println("");
+ }
+ }
+ }
pw.println(" ");
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java
new file mode 100644
index 000000000..9fb3f739a
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupColumns.java
@@ -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 };
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
index 48155fe78..cc346e2f7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
@@ -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
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
index 413ddb364..fee208d54 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/PreferenceManager.java
@@ -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));
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java
deleted file mode 100644
index 844b48b95..000000000
--- a/MyTracks/src/com/google/android/apps/mytracks/services/SignalStrengthTaskFactory.java
+++ /dev/null
@@ -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);
- }
- }
-}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
index 56d6b571d..b3610bd44 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java
@@ -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;
- }
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
new file mode 100644
index 000000000..986ae773b
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
@@ -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);
+ }
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java
new file mode 100644
index 000000000..32b16d1e4
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothSensorManager.java
@@ -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;
+ }
+ }
+ };
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/MessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/MessageParser.java
new file mode 100644
index 000000000..f490df048
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/MessageParser.java
@@ -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);
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManager.java
new file mode 100644
index 000000000..49b421b50
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManager.java
@@ -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;
+ }
+
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
new file mode 100644
index 000000000..01a8d8bf5
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
@@ -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;
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorUtils.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorUtils.java
new file mode 100644
index 000000000..28f2f4fb8
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorUtils.java
@@ -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 "";
+ }
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java
new file mode 100644
index 000000000..8cf8f951f
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java
@@ -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;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrSensorManager.java
new file mode 100644
index 000000000..c5ff64839
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrSensorManager.java
@@ -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());
+ }
+
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
index 7526d6094..7f1016856 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/ApiFeatures.java
@@ -102,10 +102,6 @@ public class ApiFeatures {
return true;
}
- public boolean hasModernSignalStrength() {
- return getApiLevel() >= 7;
- }
-
public boolean hasStrictMode() {
return getApiLevel() >= 9;
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java
new file mode 100644
index 000000000..a819a8473
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/util/BluetoothDeviceUtils.java
@@ -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 deviceNames, List 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 deviceNames, List deviceAddresses) {
+ ensureNotDiscovering();
+
+ Set 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 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 deviceNames, List 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;
+ }
+}
diff --git a/MyTracksLib/.classpath b/MyTracksLib/.classpath
new file mode 100644
index 000000000..67679b6b2
--- /dev/null
+++ b/MyTracksLib/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/MyTracksLib/.project b/MyTracksLib/.project
new file mode 100644
index 000000000..1fefe8898
--- /dev/null
+++ b/MyTracksLib/.project
@@ -0,0 +1,33 @@
+
+
+ MyTracksLib
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/MyTracksLib/.settings/org.eclipse.jdt.core.prefs b/MyTracksLib/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..a46c35996
--- /dev/null
+++ b/MyTracksLib/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Sun Nov 28 01:17:12 PST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/MyTracksLib/AndroidManifest.xml b/MyTracksLib/AndroidManifest.xml
new file mode 100644
index 000000000..ce3f0ea4b
--- /dev/null
+++ b/MyTracksLib/AndroidManifest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MyTracksLib/default.properties b/MyTracksLib/default.properties
new file mode 100644
index 000000000..948be06ed
--- /dev/null
+++ b/MyTracksLib/default.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-8
+android.library=true
diff --git a/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar b/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar
new file mode 100644
index 000000000..9b6605da7
Binary files /dev/null and b/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar differ
diff --git a/MyTracksLib/proto/sensor.proto b/MyTracksLib/proto/sensor.proto
new file mode 100644
index 000000000..b0ea1ac11
--- /dev/null
+++ b/MyTracksLib/proto/sensor.proto
@@ -0,0 +1,53 @@
+// 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;
+
+option optimize_for = LITE_RUNTIME;
+
+// An enumeration of the state of a single sensor.
+enum SensorState {
+ // There is no sensor.
+ NONE = 1;
+
+ // Now initiating an outgoing connection.
+ CONNECTING = 2;
+
+ // Now connected to a remote device.
+ CONNECTED = 3;
+
+ // A state where the sensor is not connected.
+ DISCONNECTED = 4;
+
+ // This is the only state where the sensor data can be used.
+ SENDING = 5;
+};
+
+// The state and possible reading for one integer sensor.
+message SensorData {
+ required SensorState state = 1 [default = NONE];
+
+ // Value is only defined if state = SENDING.
+ optional int32 value = 2;
+};
+
+// A message to encapsulate sensor readings associated with a track point.
+message SensorDataSet {
+ // This is a required field.
+ optional uint64 creation_time = 1 [default = 0];
+
+ optional SensorData heart_rate = 2;
+ optional SensorData cadence = 3;
+ optional SensorData power = 4;
+}
\ No newline at end of file
diff --git a/MyTracks/res/drawable/arrow_icon.png b/MyTracksLib/res/drawable/arrow_icon.png
similarity index 100%
rename from MyTracks/res/drawable/arrow_icon.png
rename to MyTracksLib/res/drawable/arrow_icon.png
diff --git a/MyTracksLib/res/values/strings.xml b/MyTracksLib/res/values/strings.xml
new file mode 100644
index 000000000..a3c7af5b8
--- /dev/null
+++ b/MyTracksLib/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+
+ My Tracks Base Library
+
diff --git a/MyTracksLib/res/values/third_party.xml b/MyTracksLib/res/values/third_party.xml
new file mode 100644
index 000000000..e85f8ff1b
--- /dev/null
+++ b/MyTracksLib/res/values/third_party.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ com.google.android.apps.mytracks.TRACK_NOTIFICATIONS
+ com.google.android.apps.mytracks.READ_TRACK_DATA
+ com.google.android.apps.mytracks.WRITE_TRACK_DATA
+
+
+ com.google.android.apps.mytracks.TRACK_STARTED
+ com.google.android.apps.mytracks.TRACK_PAUSED
+ com.google.android.apps.mytracks.TRACK_RESUMED
+ com.google.android.apps.mytracks.TRACK_STOPPED
+ com.google.android.apps.mytracks.TRACK_ID
+
+
+ com.google.android.apps.mytracks.services.TrackRecordingService
+ com.google.android.maps.mytracks
+
+
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java
similarity index 100%
rename from MyTracks/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java
rename to MyTracksLib/src/com/google/android/apps/mytracks/content/DescriptionGenerator.java
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java
new file mode 100644
index 000000000..4566225d5
--- /dev/null
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksLocation.java
@@ -0,0 +1,64 @@
+/*
+ * 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.location.Location;
+
+/**
+ * This class extends the standard Android location with extra information.
+ *
+ * @author Sandor Dornbush
+ */
+public class MyTracksLocation extends Location {
+
+ private Sensor.SensorDataSet sensorDataSet = null;
+
+ /**
+ * The id of this location from the provider.
+ */
+ private int id = -1;
+
+ public MyTracksLocation(Location location, Sensor.SensorDataSet sd) {
+ super(location);
+ this.sensorDataSet = sd;
+ }
+
+ public MyTracksLocation(String string) {
+ super(string);
+ }
+
+ public Sensor.SensorDataSet getSensorDataSet() {
+ return sensorDataSet;
+ }
+
+ public void setSensorData(Sensor.SensorDataSet sensorData) {
+ this.sensorDataSet = sensorData;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public void reset() {
+ super.reset();
+ sensorDataSet = null;
+ id = -1;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
similarity index 100%
rename from MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
rename to MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtils.java
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
similarity index 92%
rename from MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
rename to MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
index 1e814f102..f9e466dce 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/MyTracksProviderUtilsImpl.java
@@ -15,7 +15,10 @@
*/
package com.google.android.apps.mytracks.content;
+import static com.google.android.apps.mytracks.lib.MyTracksLibConstants.*;
+import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.stats.TripStatistics;
+import com.google.protobuf.InvalidProtocolBufferException;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -74,6 +77,12 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (location.hasSpeed()) {
values.put(TrackPointsColumns.SPEED, location.getSpeed());
}
+ if (location instanceof MyTracksLocation) {
+ MyTracksLocation mtLocation = (MyTracksLocation) location;
+ if (mtLocation.getSensorDataSet() != null) {
+ values.put(TrackPointsColumns.SENSOR, mtLocation.getSensorDataSet().toByteArray());
+ }
+ }
return values;
}
@@ -171,7 +180,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Location createLocation(Cursor cursor) {
- Location location = new Location("");
+ Location location = new MyTracksLocation("");
fillLocation(cursor, location);
return location;
}
@@ -188,6 +197,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
int idxBearing = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
int idxAccuracy = cursor.getColumnIndexOrThrow(TrackPointsColumns.ACCURACY);
int idxSpeed = cursor.getColumnIndexOrThrow(TrackPointsColumns.SPEED);
+ int idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
if (!cursor.isNull(idxLatitude)) {
location.setLatitude(1. * cursor.getInt(idxLatitude) / 1E6);
@@ -210,6 +220,18 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
if (!cursor.isNull(idxAccuracy)) {
location.setAccuracy(cursor.getFloat(idxAccuracy));
}
+ if (location instanceof MyTracksLocation &&
+ !cursor.isNull(idxSensor)) {
+ MyTracksLocation mtLocation = (MyTracksLocation) location;
+ // TODO get the right buffer.
+ Sensor.SensorDataSet sensorData;
+ try {
+ sensorData = Sensor.SensorDataSet.parseFrom(cursor.getBlob(idxSensor));
+ mtLocation.setSensorData(sensorData);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, "Failed to parse sensor data.", e);
+ }
+ }
}
@Override
@@ -449,7 +471,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void deleteAllTracks() {
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
- contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
+ contentResolver.delete(TrackPointsColumns.CONTENT_URI,
+ null, null);
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
}
@@ -463,7 +486,8 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
}
contentResolver.delete(WaypointsColumns.CONTENT_URI,
WaypointsColumns.TRACKID + "=" + trackId, null);
- contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
+ contentResolver.delete(
+ TracksColumns.CONTENT_URI, "_id=" + trackId, null);
}
@Override
@@ -475,17 +499,16 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
final Waypoint nextWaypoint =
getNextStatisticsWaypointAfter(deletedWaypoint);
if (nextWaypoint != null) {
- Log.d(MyTracksProvider.TAG, "Correcting marker " + nextWaypoint.getId()
+ Log.d(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.");
+ Log.w(TAG, "Update of marker was unsuccessful.");
}
} else {
- Log.d(MyTracksProvider.TAG,
- "No statistics marker after the deleted one was found.");
+ Log.d(TAG, "No statistics marker after the deleted one was found.");
}
}
contentResolver.delete(
@@ -510,7 +533,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
+ Log.w(TAG, "Caught unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -529,7 +552,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
null /*selectionArgs*/);
return rows == 1;
} catch (RuntimeException e) {
- Log.e(MyTracksProvider.TAG, "Caught unexpected exception.", e);
+ Log.e(TAG, "Caught unexpected exception.", e);
}
return false;
}
@@ -549,7 +572,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createLocation(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpeceted exception.", e);
+ Log.w(TAG, "Caught an unexpeceted exception.", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -573,7 +596,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createTrack(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
+ Log.w(TAG, "Caught unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -601,7 +624,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
@@ -623,7 +646,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createWaypoint(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
@@ -647,7 +670,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
@@ -671,7 +694,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
cursor.getColumnIndexOrThrow(WaypointsColumns._ID));
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
@@ -695,7 +718,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
cursor.getColumnIndexOrThrow(WaypointsColumns._ID));
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
cursor.close();
}
@@ -714,7 +737,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
return createTrack(cursor);
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught an unexpected exception.", e);
+ Log.w(TAG, "Caught an unexpected exception.", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -818,7 +841,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
long lastId = -1;
Cursor cursor = getLocationsCursor(track.getId(), -1, maxPoints, true);
if (cursor == null) {
- Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!");
+ Log.w(TAG, "Cannot get a locations cursor!");
return lastId;
}
try {
@@ -835,7 +858,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
} while (cursor.moveToPrevious());
}
} catch (RuntimeException e) {
- Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
+ Log.w(TAG, "Caught unexpected exception.", e);
} finally {
cursor.close();
}
@@ -851,14 +874,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Uri insertTrack(Track track) {
- Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
+ Log.d(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");
+ Log.d(TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
createContentValues(location, trackId));
}
@@ -877,7 +900,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public Uri insertWaypoint(Waypoint waypoint) {
- Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
+ Log.d(TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
waypoint.setId(-1);
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
createContentValues(waypoint));
@@ -907,7 +930,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
@Override
public void updateTrack(Track track) {
- Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
+ Log.d(TAG, "MyTracksProviderUtilsImpl.updateTrack");
contentResolver.update(TracksColumns.CONTENT_URI,
createContentValues(track), "_id=" + track.getId(), null);
}
diff --git a/MyTracksLib/src/com/google/android/apps/mytracks/content/Sensor.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/Sensor.java
new file mode 100644
index 000000000..54a9df31d
--- /dev/null
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/Sensor.java
@@ -0,0 +1,816 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: proto/sensor.proto
+
+package com.google.android.apps.mytracks.content;
+
+public final class Sensor {
+ private Sensor() {}
+ public static void registerAllExtensions(
+ com.google.protobuf.ExtensionRegistryLite registry) {
+ }
+ public enum SensorState
+ implements com.google.protobuf.Internal.EnumLite {
+ NONE(0, 1),
+ CONNECTING(1, 2),
+ CONNECTED(2, 3),
+ DISCONNECTED(3, 4),
+ SENDING(4, 5),
+ ;
+
+
+ public final int getNumber() { return value; }
+
+ public static SensorState valueOf(int value) {
+ switch (value) {
+ case 1: return NONE;
+ case 2: return CONNECTING;
+ case 3: return CONNECTED;
+ case 4: return DISCONNECTED;
+ case 5: return SENDING;
+ default: return null;
+ }
+ }
+
+ public static com.google.protobuf.Internal.EnumLiteMap
+ internalGetValueMap() {
+ return internalValueMap;
+ }
+ private static com.google.protobuf.Internal.EnumLiteMap
+ internalValueMap =
+ new com.google.protobuf.Internal.EnumLiteMap() {
+ public SensorState findValueByNumber(int number) {
+ return SensorState.valueOf(number)
+ ; }
+ };
+
+ private final int index;
+ private final int value;
+ private SensorState(int index, int value) {
+ this.index = index;
+ this.value = value;
+ }
+
+ // @@protoc_insertion_point(enum_scope:com.google.android.apps.mytracks.content.SensorState)
+ }
+
+ public static final class SensorData extends
+ com.google.protobuf.GeneratedMessageLite {
+ // Use SensorData.newBuilder() to construct.
+ private SensorData() {
+ initFields();
+ }
+ private SensorData(boolean noInit) {}
+
+ private static final SensorData defaultInstance;
+ public static SensorData getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public SensorData getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ // required .com.google.android.apps.mytracks.content.SensorState state = 1 [default = NONE];
+ public static final int STATE_FIELD_NUMBER = 1;
+ private boolean hasState;
+ private com.google.android.apps.mytracks.content.Sensor.SensorState state_;
+ public boolean hasState() { return hasState; }
+ public com.google.android.apps.mytracks.content.Sensor.SensorState getState() { return state_; }
+
+ // optional int32 value = 2;
+ public static final int VALUE_FIELD_NUMBER = 2;
+ private boolean hasValue;
+ private int value_ = 0;
+ public boolean hasValue() { return hasValue; }
+ public int getValue() { return value_; }
+
+ private void initFields() {
+ state_ = com.google.android.apps.mytracks.content.Sensor.SensorState.NONE;
+ }
+ public final boolean isInitialized() {
+ if (!hasState) return false;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (hasState()) {
+ output.writeEnum(1, getState().getNumber());
+ }
+ if (hasValue()) {
+ output.writeInt32(2, getValue());
+ }
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (hasState()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeEnumSize(1, getState().getNumber());
+ }
+ if (hasValue()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeInt32Size(2, getValue());
+ }
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorData parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(com.google.android.apps.mytracks.content.Sensor.SensorData prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessageLite.Builder<
+ com.google.android.apps.mytracks.content.Sensor.SensorData, Builder> {
+ private com.google.android.apps.mytracks.content.Sensor.SensorData result;
+
+ // Construct using com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder()
+ private Builder() {}
+
+ private static Builder create() {
+ Builder builder = new Builder();
+ builder.result = new com.google.android.apps.mytracks.content.Sensor.SensorData();
+ return builder;
+ }
+
+ protected com.google.android.apps.mytracks.content.Sensor.SensorData internalGetResult() {
+ return result;
+ }
+
+ public Builder clear() {
+ if (result == null) {
+ throw new IllegalStateException(
+ "Cannot call clear() after build().");
+ }
+ result = new com.google.android.apps.mytracks.content.Sensor.SensorData();
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(result);
+ }
+
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getDefaultInstanceForType() {
+ return com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ }
+
+ public boolean isInitialized() {
+ return result.isInitialized();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData build() {
+ if (result != null && !isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return buildPartial();
+ }
+
+ private com.google.android.apps.mytracks.content.Sensor.SensorData buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ if (!isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return buildPartial();
+ }
+
+ public com.google.android.apps.mytracks.content.Sensor.SensorData buildPartial() {
+ if (result == null) {
+ throw new IllegalStateException(
+ "build() has already been called on this Builder.");
+ }
+ com.google.android.apps.mytracks.content.Sensor.SensorData returnMe = result;
+ result = null;
+ return returnMe;
+ }
+
+ public Builder mergeFrom(com.google.android.apps.mytracks.content.Sensor.SensorData other) {
+ if (other == com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) return this;
+ if (other.hasState()) {
+ setState(other.getState());
+ }
+ if (other.hasValue()) {
+ setValue(other.getValue());
+ }
+ return this;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ return this;
+ default: {
+ if (!parseUnknownField(input, extensionRegistry, tag)) {
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ int rawValue = input.readEnum();
+ com.google.android.apps.mytracks.content.Sensor.SensorState value = com.google.android.apps.mytracks.content.Sensor.SensorState.valueOf(rawValue);
+ if (value != null) {
+ setState(value);
+ }
+ break;
+ }
+ case 16: {
+ setValue(input.readInt32());
+ break;
+ }
+ }
+ }
+ }
+
+
+ // required .com.google.android.apps.mytracks.content.SensorState state = 1 [default = NONE];
+ public boolean hasState() {
+ return result.hasState();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorState getState() {
+ return result.getState();
+ }
+ public Builder setState(com.google.android.apps.mytracks.content.Sensor.SensorState value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ result.hasState = true;
+ result.state_ = value;
+ return this;
+ }
+ public Builder clearState() {
+ result.hasState = false;
+ result.state_ = com.google.android.apps.mytracks.content.Sensor.SensorState.NONE;
+ return this;
+ }
+
+ // optional int32 value = 2;
+ public boolean hasValue() {
+ return result.hasValue();
+ }
+ public int getValue() {
+ return result.getValue();
+ }
+ public Builder setValue(int value) {
+ result.hasValue = true;
+ result.value_ = value;
+ return this;
+ }
+ public Builder clearValue() {
+ result.hasValue = false;
+ result.value_ = 0;
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:com.google.android.apps.mytracks.content.SensorData)
+ }
+
+ static {
+ defaultInstance = new SensorData(true);
+ com.google.android.apps.mytracks.content.Sensor.internalForceInit();
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:com.google.android.apps.mytracks.content.SensorData)
+ }
+
+ public static final class SensorDataSet extends
+ com.google.protobuf.GeneratedMessageLite {
+ // Use SensorDataSet.newBuilder() to construct.
+ private SensorDataSet() {
+ initFields();
+ }
+ private SensorDataSet(boolean noInit) {}
+
+ private static final SensorDataSet defaultInstance;
+ public static SensorDataSet getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public SensorDataSet getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ // optional uint64 creation_time = 1 [default = 0];
+ public static final int CREATION_TIME_FIELD_NUMBER = 1;
+ private boolean hasCreationTime;
+ private long creationTime_ = 0L;
+ public boolean hasCreationTime() { return hasCreationTime; }
+ public long getCreationTime() { return creationTime_; }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData heart_rate = 2;
+ public static final int HEART_RATE_FIELD_NUMBER = 2;
+ private boolean hasHeartRate;
+ private com.google.android.apps.mytracks.content.Sensor.SensorData heartRate_;
+ public boolean hasHeartRate() { return hasHeartRate; }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getHeartRate() { return heartRate_; }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData cadence = 3;
+ public static final int CADENCE_FIELD_NUMBER = 3;
+ private boolean hasCadence;
+ private com.google.android.apps.mytracks.content.Sensor.SensorData cadence_;
+ public boolean hasCadence() { return hasCadence; }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getCadence() { return cadence_; }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData power = 4;
+ public static final int POWER_FIELD_NUMBER = 4;
+ private boolean hasPower;
+ private com.google.android.apps.mytracks.content.Sensor.SensorData power_;
+ public boolean hasPower() { return hasPower; }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getPower() { return power_; }
+
+ private void initFields() {
+ heartRate_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ cadence_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ power_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ }
+ public final boolean isInitialized() {
+ if (hasHeartRate()) {
+ if (!getHeartRate().isInitialized()) return false;
+ }
+ if (hasCadence()) {
+ if (!getCadence().isInitialized()) return false;
+ }
+ if (hasPower()) {
+ if (!getPower().isInitialized()) return false;
+ }
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (hasCreationTime()) {
+ output.writeUInt64(1, getCreationTime());
+ }
+ if (hasHeartRate()) {
+ output.writeMessage(2, getHeartRate());
+ }
+ if (hasCadence()) {
+ output.writeMessage(3, getCadence());
+ }
+ if (hasPower()) {
+ output.writeMessage(4, getPower());
+ }
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (hasCreationTime()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeUInt64Size(1, getCreationTime());
+ }
+ if (hasHeartRate()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(2, getHeartRate());
+ }
+ if (hasCadence()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(3, getCadence());
+ }
+ if (hasPower()) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(4, getPower());
+ }
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static com.google.android.apps.mytracks.content.Sensor.SensorDataSet parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(com.google.android.apps.mytracks.content.Sensor.SensorDataSet prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessageLite.Builder<
+ com.google.android.apps.mytracks.content.Sensor.SensorDataSet, Builder> {
+ private com.google.android.apps.mytracks.content.Sensor.SensorDataSet result;
+
+ // Construct using com.google.android.apps.mytracks.content.Sensor.SensorDataSet.newBuilder()
+ private Builder() {}
+
+ private static Builder create() {
+ Builder builder = new Builder();
+ builder.result = new com.google.android.apps.mytracks.content.Sensor.SensorDataSet();
+ return builder;
+ }
+
+ protected com.google.android.apps.mytracks.content.Sensor.SensorDataSet internalGetResult() {
+ return result;
+ }
+
+ public Builder clear() {
+ if (result == null) {
+ throw new IllegalStateException(
+ "Cannot call clear() after build().");
+ }
+ result = new com.google.android.apps.mytracks.content.Sensor.SensorDataSet();
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(result);
+ }
+
+ public com.google.android.apps.mytracks.content.Sensor.SensorDataSet getDefaultInstanceForType() {
+ return com.google.android.apps.mytracks.content.Sensor.SensorDataSet.getDefaultInstance();
+ }
+
+ public boolean isInitialized() {
+ return result.isInitialized();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorDataSet build() {
+ if (result != null && !isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return buildPartial();
+ }
+
+ private com.google.android.apps.mytracks.content.Sensor.SensorDataSet buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ if (!isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return buildPartial();
+ }
+
+ public com.google.android.apps.mytracks.content.Sensor.SensorDataSet buildPartial() {
+ if (result == null) {
+ throw new IllegalStateException(
+ "build() has already been called on this Builder.");
+ }
+ com.google.android.apps.mytracks.content.Sensor.SensorDataSet returnMe = result;
+ result = null;
+ return returnMe;
+ }
+
+ public Builder mergeFrom(com.google.android.apps.mytracks.content.Sensor.SensorDataSet other) {
+ if (other == com.google.android.apps.mytracks.content.Sensor.SensorDataSet.getDefaultInstance()) return this;
+ if (other.hasCreationTime()) {
+ setCreationTime(other.getCreationTime());
+ }
+ if (other.hasHeartRate()) {
+ mergeHeartRate(other.getHeartRate());
+ }
+ if (other.hasCadence()) {
+ mergeCadence(other.getCadence());
+ }
+ if (other.hasPower()) {
+ mergePower(other.getPower());
+ }
+ return this;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ return this;
+ default: {
+ if (!parseUnknownField(input, extensionRegistry, tag)) {
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ setCreationTime(input.readUInt64());
+ break;
+ }
+ case 18: {
+ com.google.android.apps.mytracks.content.Sensor.SensorData.Builder subBuilder = com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder();
+ if (hasHeartRate()) {
+ subBuilder.mergeFrom(getHeartRate());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setHeartRate(subBuilder.buildPartial());
+ break;
+ }
+ case 26: {
+ com.google.android.apps.mytracks.content.Sensor.SensorData.Builder subBuilder = com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder();
+ if (hasCadence()) {
+ subBuilder.mergeFrom(getCadence());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setCadence(subBuilder.buildPartial());
+ break;
+ }
+ case 34: {
+ com.google.android.apps.mytracks.content.Sensor.SensorData.Builder subBuilder = com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder();
+ if (hasPower()) {
+ subBuilder.mergeFrom(getPower());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setPower(subBuilder.buildPartial());
+ break;
+ }
+ }
+ }
+ }
+
+
+ // optional uint64 creation_time = 1 [default = 0];
+ public boolean hasCreationTime() {
+ return result.hasCreationTime();
+ }
+ public long getCreationTime() {
+ return result.getCreationTime();
+ }
+ public Builder setCreationTime(long value) {
+ result.hasCreationTime = true;
+ result.creationTime_ = value;
+ return this;
+ }
+ public Builder clearCreationTime() {
+ result.hasCreationTime = false;
+ result.creationTime_ = 0L;
+ return this;
+ }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData heart_rate = 2;
+ public boolean hasHeartRate() {
+ return result.hasHeartRate();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getHeartRate() {
+ return result.getHeartRate();
+ }
+ public Builder setHeartRate(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ result.hasHeartRate = true;
+ result.heartRate_ = value;
+ return this;
+ }
+ public Builder setHeartRate(com.google.android.apps.mytracks.content.Sensor.SensorData.Builder builderForValue) {
+ result.hasHeartRate = true;
+ result.heartRate_ = builderForValue.build();
+ return this;
+ }
+ public Builder mergeHeartRate(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (result.hasHeartRate() &&
+ result.heartRate_ != com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) {
+ result.heartRate_ =
+ com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder(result.heartRate_).mergeFrom(value).buildPartial();
+ } else {
+ result.heartRate_ = value;
+ }
+ result.hasHeartRate = true;
+ return this;
+ }
+ public Builder clearHeartRate() {
+ result.hasHeartRate = false;
+ result.heartRate_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ return this;
+ }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData cadence = 3;
+ public boolean hasCadence() {
+ return result.hasCadence();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getCadence() {
+ return result.getCadence();
+ }
+ public Builder setCadence(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ result.hasCadence = true;
+ result.cadence_ = value;
+ return this;
+ }
+ public Builder setCadence(com.google.android.apps.mytracks.content.Sensor.SensorData.Builder builderForValue) {
+ result.hasCadence = true;
+ result.cadence_ = builderForValue.build();
+ return this;
+ }
+ public Builder mergeCadence(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (result.hasCadence() &&
+ result.cadence_ != com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) {
+ result.cadence_ =
+ com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder(result.cadence_).mergeFrom(value).buildPartial();
+ } else {
+ result.cadence_ = value;
+ }
+ result.hasCadence = true;
+ return this;
+ }
+ public Builder clearCadence() {
+ result.hasCadence = false;
+ result.cadence_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ return this;
+ }
+
+ // optional .com.google.android.apps.mytracks.content.SensorData power = 4;
+ public boolean hasPower() {
+ return result.hasPower();
+ }
+ public com.google.android.apps.mytracks.content.Sensor.SensorData getPower() {
+ return result.getPower();
+ }
+ public Builder setPower(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ result.hasPower = true;
+ result.power_ = value;
+ return this;
+ }
+ public Builder setPower(com.google.android.apps.mytracks.content.Sensor.SensorData.Builder builderForValue) {
+ result.hasPower = true;
+ result.power_ = builderForValue.build();
+ return this;
+ }
+ public Builder mergePower(com.google.android.apps.mytracks.content.Sensor.SensorData value) {
+ if (result.hasPower() &&
+ result.power_ != com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance()) {
+ result.power_ =
+ com.google.android.apps.mytracks.content.Sensor.SensorData.newBuilder(result.power_).mergeFrom(value).buildPartial();
+ } else {
+ result.power_ = value;
+ }
+ result.hasPower = true;
+ return this;
+ }
+ public Builder clearPower() {
+ result.hasPower = false;
+ result.power_ = com.google.android.apps.mytracks.content.Sensor.SensorData.getDefaultInstance();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:com.google.android.apps.mytracks.content.SensorDataSet)
+ }
+
+ static {
+ defaultInstance = new SensorDataSet(true);
+ com.google.android.apps.mytracks.content.Sensor.internalForceInit();
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:com.google.android.apps.mytracks.content.SensorDataSet)
+ }
+
+
+ static {
+ }
+
+ public static void internalForceInit() {}
+
+ // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/content/Track.java b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
similarity index 99%
rename from MyTracks/src/com/google/android/apps/mytracks/content/Track.java
rename to MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
index f881c966b..fe4c19453 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/content/Track.java
+++ b/MyTracksLib/src/com/google/android/apps/mytracks/content/Track.java
@@ -34,7 +34,7 @@ import java.util.ArrayList;
public class Track implements Parcelable {
/**
- * Creator for a Track object
+ * Creator for a Track object.
*/
public static class Creator implements Parcelable.Creator