diff --git a/MyTracks/res/values-ca/arrays.xml b/MyTracks/res/values-ca/arrays.xml
index 2bba332fa..693efc12b 100644
--- a/MyTracks/res/values-ca/arrays.xml
+++ b/MyTracks/res/values-ca/arrays.xml
@@ -284,11 +284,4 @@ limitations under the License.
- punt de referència
-
- - Cap
- - ANT+ Sensors
- - SRM ANT+ Bridge
- - Zephyr Bluetooth
-
-
diff --git a/MyTracks/res/values-ca/strings.xml b/MyTracks/res/values-ca/strings.xml
index 6ac1e8525..5f7b54f2f 100644
--- a/MyTracks/res/values-ca/strings.xml
+++ b/MyTracks/res/values-ca/strings.xml
@@ -409,4 +409,6 @@ limitations under the License.
S\'està cercant
S\'està enviant
+
+ Cap
diff --git a/MyTracks/res/values-es-rES/arrays.xml b/MyTracks/res/values-es-rES/arrays.xml
index 757121f6b..cb7c0b79a 100644
--- a/MyTracks/res/values-es-rES/arrays.xml
+++ b/MyTracks/res/values-es-rES/arrays.xml
@@ -143,11 +143,4 @@ limitations under the License.
- camino
-
- - Ninguna
- - ANT+ Sensores
- - SRM ANT+ Puente
- - Zephyr Bluetooth
-
-
diff --git a/MyTracks/res/values-es-rES/strings.xml b/MyTracks/res/values-es-rES/strings.xml
index 2cb837ae1..169e3b78a 100644
--- a/MyTracks/res/values-es-rES/strings.xml
+++ b/MyTracks/res/values-es-rES/strings.xml
@@ -128,4 +128,8 @@ limitations under the License.
$6 : Speed units (mph) : Pace units (per mile)
-->
+
+ Ninguna
+ ANT+ Sensores
+ SRM ANT+ Puente
diff --git a/MyTracks/res/values-fr/arrays.xml b/MyTracks/res/values-fr/arrays.xml
index 18740a364..ef0994644 100644
--- a/MyTracks/res/values-fr/arrays.xml
+++ b/MyTracks/res/values-fr/arrays.xml
@@ -291,11 +291,4 @@ limitations under the License.
- point de passage
-
- - Aucun
- - Capteur ANT+
- - Pont SRM ANT+
- - Bluetooth Zephyr
-
-
diff --git a/MyTracks/res/values-fr/strings.xml b/MyTracks/res/values-fr/strings.xml
index a1c00f1fc..49cc6516d 100644
--- a/MyTracks/res/values-fr/strings.xml
+++ b/MyTracks/res/values-fr/strings.xml
@@ -397,6 +397,13 @@ limitations under the License.
Recherche
Envoi
+
+ Aucun
+ Capteur ANT+
+ Pont SRM ANT+
+ Bluetooth Polar
+ Bluetooth Zephyr
+
notifications lors de l\'enregistrement de parcours
lecture des parcours enregistrés
diff --git a/MyTracks/res/values-ja/arrays.xml b/MyTracks/res/values-ja/arrays.xml
index 31e45abee..aeffc5d84 100644
--- a/MyTracks/res/values-ja/arrays.xml
+++ b/MyTracks/res/values-ja/arrays.xml
@@ -272,11 +272,4 @@ limitations under the License.
- ウェイポイント
-
- - なし
- - ANT+ センサー
- - SRM ANT+ ブリッジ
- - Zephyr Bluetooth
-
-
diff --git a/MyTracks/res/values-ja/strings.xml b/MyTracks/res/values-ja/strings.xml
index 1a6a34106..a23fb0bbf 100644
--- a/MyTracks/res/values-ja/strings.xml
+++ b/MyTracks/res/values-ja/strings.xml
@@ -463,6 +463,11 @@ limitations under the License.
検索中
送信中
+
+ なし
+ ANT+ センサー
+ SRM ANT+ ブリッジ
+
トラック記録通知受信
トラック読み込み
diff --git a/MyTracks/res/values-ru/arrays.xml b/MyTracks/res/values-ru/arrays.xml
index feb32549f..e8cf89568 100755
--- a/MyTracks/res/values-ru/arrays.xml
+++ b/MyTracks/res/values-ru/arrays.xml
@@ -272,11 +272,4 @@ limitations under the License.
- путевая точка
-
- - Нет
- - ANT+ Sensors
- - SRM ANT+ Bridge
- - Zephyr Bluetooth
-
-
diff --git a/MyTracks/res/values-ru/strings.xml b/MyTracks/res/values-ru/strings.xml
index 0598737ce..76361ce98 100644
--- a/MyTracks/res/values-ru/strings.xml
+++ b/MyTracks/res/values-ru/strings.xml
@@ -490,6 +490,11 @@ limitations under the License.
Поиск
Отправка
+
+ Нет
+ ANT+ Датчик
+ SRM ANT+ Bridge
+
получать уведомления о записи трека
чтение записанных данных трека
diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml
index dd6c37220..f85c10136 100644
--- a/MyTracks/res/values/arrays.xml
+++ b/MyTracks/res/values/arrays.xml
@@ -277,11 +277,4 @@ limitations under the License.
- way point
-
- - None
- - ANT+ Sensors
- - SRM ANT+ Bridge
- - Zephyr Bluetooth
-
-
diff --git a/MyTracks/res/values/arrays_do_not_translate.xml b/MyTracks/res/values/arrays_do_not_translate.xml
index 780ee8b9e..25f173930 100644
--- a/MyTracks/res/values/arrays_do_not_translate.xml
+++ b/MyTracks/res/values/arrays_do_not_translate.xml
@@ -98,10 +98,19 @@ limitations under the License.
- TCX
+
+ - @string/no_sensor_option
+ - @string/ant_sensor_option
+ - @string/ant_bridge_sensor_option
+ - @string/polar_bt_sensor_option
+ - @string/zephyr_bt_sensor_option
+
+
- @string/none
- @string/ant_sensor_type
- @string/srm_ant_bridge_sensor_type
+ - @string/polar_sensor_type
- @string/zephyr_sensor_type
diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml
index f6252d1db..6e25e4817 100644
--- a/MyTracks/res/values/do_not_translate.xml
+++ b/MyTracks/res/values/do_not_translate.xml
@@ -33,6 +33,7 @@ limitations under the License.
NONE
ANT
SRM_ANT_BRIDGE
+ POLAR
ZEPHYR
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 5a5e33fba..019b7fe00 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -501,4 +501,12 @@ limitations under the License.
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.
+
+
+ NONE
+ ANT+ Sensors
+ SRM ANT+ Bridge
+ Polar Bluetooth
+ Zephyr Bluetooth
+
diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
index e3b2084cd..45822ddb7 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java
@@ -232,7 +232,8 @@ public class SettingsActivity extends PreferenceActivity {
private void updateSensorSettings(String sensorType) {
boolean usesBluetooth =
- getString(R.string.zephyr_sensor_type).equals(sensorType);
+ getString(R.string.zephyr_sensor_type).equals(sensorType)
+ || getString(R.string.polar_sensor_type).equals(sensorType);
findPreference(
getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth);
findPreference(
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
index 3f95db95f..8fbbdc44b 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/BluetoothConnectionManager.java
@@ -394,8 +394,9 @@ public class BluetoothConnectionManager {
offset = 0;
- // Send the obtained bytes to the UI Activity
- handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
+ // Send copy of the obtained bytes to the UI Activity.
+ // Avoids memory inconsistency issues.
+ handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone())
.sendToTarget();
} catch (IOException e) {
Log.e(Constants.TAG, "disconnected", e);
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java
new file mode 100644
index 000000000..d8876587d
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParser.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 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 implementation of a Sensor MessageParser for Polar Wearlink Bluetooth HRM.
+ *
+ * Polar Bluetooth Wearlink packet example;
+ * Hdr Len Chk Seq Status HeartRate RRInterval_16-bits
+ * FE 08 F7 06 F1 48 03 64
+ * where;
+ * Hdr always = 254 (0xFE),
+ * Chk = 255 - Len
+ * Seq range 0 to 15
+ * Status = Upper nibble may be battery voltage
+ * bit 0 is Beat Detection flag.
+ *
+ * Additional packet examples;
+ * FE 08 F7 06 F1 48 03 64
+ * FE 0A F5 06 F1 48 03 64 03 70
+ *
+ * @author John R. Gerthoffer
+ */
+public class PolarMessageParser implements MessageParser {
+
+ private int lastHeartRate = 0;
+
+ /**
+ * Applies Polar packet validation rules to buffer.
+ * Polar packets are checked for following;
+ * offset 0 = header byte, 254 (0xFE).
+ * offset 1 = packet length byte, 8, 10, 12, 14.
+ * offset 2 = check byte, 255 - packet length.
+ * offset 3 = sequence byte, range from 0 to 15.
+ *
+ * @param an array of bytes to parse
+ * @param buffer offset to beginning of packet.
+ * @return whether buffer has a valid packet at offset i
+ */
+ private boolean packetValid (byte[] buffer, int i) {
+ boolean headerValid = (buffer[i] & 0xFF) == 0xFE;
+ boolean checkbyteValid = (buffer[i + 2] & 0xFF) == (0xFF - (buffer[i + 1] & 0xFF));
+ boolean sequenceValid = (buffer[i + 3] & 0xFF) < 16;
+
+ return headerValid && checkbyteValid && sequenceValid;
+ }
+
+ @Override
+ public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
+
+ int heartRate = 0;
+ boolean heartrateValid = false;
+
+ // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends.
+ for (int i = 0; i < buffer.length - 8; i++) {
+ heartrateValid = packetValid(buffer,i);
+ if (heartrateValid) {
+ heartRate = buffer[i + 5] & 0xFF;
+ break;
+ }
+ }
+
+ // If our buffer is corrupted, use decaying last good value.
+ if(!heartrateValid) {
+ heartRate = (int) (lastHeartRate * 0.8);
+ if(heartRate < 50)
+ heartRate = 0;
+ }
+
+ lastHeartRate = heartRate; // Remember good value for next time.
+
+ // Heart Rate
+ Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
+ .setValue(heartRate)
+ .setState(Sensor.SensorState.SENDING);
+
+ Sensor.SensorDataSet sds = Sensor.SensorDataSet.newBuilder()
+ .setCreationTime(System.currentTimeMillis())
+ .setHeartRate(b)
+ .build();
+
+ return sds;
+ }
+
+ /**
+ * Applies packet validation rules to buffer
+ *
+ * @param an array of bytes to parse
+ * @return whether buffer has a valid packet starting at index zero
+ */
+ @Override
+ public boolean isValid(byte[] buffer) {
+ return packetValid(buffer,0);
+ }
+
+ /**
+ * Polar uses variable packet sizes; 8, 10, 12, 14 and rarely 16.
+ * The most frequent are 8 and 10.
+ * We will wait for 16 bytes.
+ * This way, we are assured of getting one good one.
+ *
+ * @return the size of buffer needed to parse a good packet
+ */
+ @Override
+ public int getFrameSize() {
+ return 16;
+ }
+
+ /**
+ * Searches buffer for the beginning of a valid packet.
+ *
+ * @param an array of bytes to parse
+ * @return index to beginning of good packet, or -1 if none found.
+ */
+ @Override
+ public int findNextAlignment(byte[] buffer) {
+ // Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends.
+ for (int i = 0; i < buffer.length - 8; i++) {
+ if (packetValid(buffer,i)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java
new file mode 100644
index 000000000..d1d1f7441
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/PolarSensorManager.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * PolarSensorManager - A sensor manager for Polar heart rate monitors.
+ */
+
+public class PolarSensorManager extends BluetoothSensorManager {
+ public PolarSensorManager(Context context) {
+ super(context, new PolarMessageParser());
+ }
+}
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
index 7c468be2c..a7836c3d0 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactory.java
@@ -59,6 +59,8 @@ public class SensorManagerFactory {
return new AntSRMSensorManager(context);
} else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) {
return new ZephyrSensorManager(context);
+ } else if (sensor.equals(context.getString(R.string.polar_sensor_type))) {
+ return new PolarSensorManager(context);
} else {
Log.w(Constants.TAG, "Unable to find sensor type: " + sensor);
return null;
diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java
new file mode 100644
index 000000000..26ce13bef
--- /dev/null
+++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/PolarMessageParserTest.java
@@ -0,0 +1,70 @@
+package com.google.android.apps.mytracks.services.sensors;
+
+import java.util.Arrays;
+
+import com.google.android.apps.mytracks.content.Sensor;
+
+import junit.framework.TestCase;
+
+public class PolarMessageParserTest extends TestCase {
+
+ PolarMessageParser parser = new PolarMessageParser();
+ // A complete and valid Polar HxM packet
+ // FE08F701D1001104FE08F702D1001104
+ private final byte[] originalBuf =
+ {(byte) 0xFE, 0x08, (byte) 0xF7, 0x01, (byte) 0xD1, 0x00, 0x11, 0x04, (byte) 0xFE, 0x08,
+ (byte) 0xF7, 0x02, (byte) 0xD1, 0x00, 0x11, 0x04};
+ private byte[] buf;
+
+ public void setUp() {
+ buf = Arrays.copyOf(originalBuf, originalBuf.length);
+ }
+
+ public void testIsValid() {
+ assertTrue(parser.isValid(buf));
+ }
+
+ public void testIsValid_invalidHeader() {
+ // Invalidate header.
+ buf[0] = 0x03;
+ assertFalse(parser.isValid(buf));
+ }
+
+ public void testIsValid_invalidCheckbyte() {
+ // Invalidate checkbyte.
+ buf[2] = 0x03;
+ assertFalse(parser.isValid(buf));
+ }
+
+ public void testIsValid_invalidSequence() {
+ // Invalidate sequence.
+ buf[3] = 0x11;
+ assertFalse(parser.isValid(buf));
+ }
+
+ public void testParseBuffer() {
+ buf[5] = 70;
+ Sensor.SensorDataSet sds = parser.parseBuffer(buf);
+ assertTrue(sds.hasHeartRate());
+ assertTrue(sds.getHeartRate().getState() == Sensor.SensorState.SENDING);
+ assertEquals(70, sds.getHeartRate().getValue());
+ }
+
+ public void testFindNextAlignment_offset() {
+ // The first 4 bytes are garbage
+ buf = new byte[originalBuf.length + 4];
+ buf[0] = 4;
+ buf[1] = 2;
+ buf[2] = 4;
+ buf[3] = 2;
+
+ // Then the valid message.
+ System.arraycopy(originalBuf, 0, buf, 4, originalBuf.length);
+ assertEquals(4, parser.findNextAlignment(buf));
+ }
+
+ public void testFindNextAlignment_invalid() {
+ buf[0] = 0;
+ assertEquals(-1, parser.findNextAlignment(buf));
+ }
+}