diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 3598aec25..6f3b16692 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -119,6 +119,9 @@ limitations under the License. + + + diff --git a/MyTracks/res/values-de/arrays.xml b/MyTracks/res/values-de/arrays.xml index 8666f77c5..ed7915fc8 100644 --- a/MyTracks/res/values-de/arrays.xml +++ b/MyTracks/res/values-de/arrays.xml @@ -17,7 +17,7 @@ limitations under the License. - Off + Aus 1 min 2 min 5 min @@ -70,8 +70,8 @@ limitations under the License. - batterie sparend (adaptiv) - hohe genauigkeit (adaptiv) + batteriesparend (adaptiv) + hohe Genauigkeit (adaptiv) maximal (empfohlen) 2 s 3 s @@ -133,12 +133,12 @@ limitations under the License. Nicht zerteilen - 100 meilen - 50 meilen - 25 meilen - 10 meilen - 5 meilen - 1 meile + 100 Meilen + 50 Meilen + 25 Meilen + 10 Meilen + 5 Meilen + 1 Meile 60 min 30 min 15 min @@ -159,7 +159,7 @@ limitations under the License. Laufbahn Geländelauf Gehen - Schnelles Gehen + Gehen (Sport) Wandern Bergwandern Bergsteigen @@ -181,11 +181,11 @@ limitations under the License. Eissegeln Segeln - Kanadierfahren - Kajakfahren + Paddeln + Kajak fahren Kanuwandern Rudern - Motorbootfahren + Motorboot fahren Fähre Ferngesteuertes Boot Surfen @@ -229,13 +229,14 @@ limitations under the License. Bank Kneipe Grill + Imbiss Gebäude Bushaltestelle Straßenbahnhaltestelle Bahnhof S-Bahnhof Café - Lagerfeuer + Feuerstelle Campingplatz Zeltplatz Kirche @@ -278,6 +279,7 @@ limitations under the License. Tunnel Baum Wasserfall + Springbrunnen Wegpunkt diff --git a/MyTracks/res/values-de/strings.xml b/MyTracks/res/values-de/strings.xml index 9514ddec3..a17e5ac0a 100644 --- a/MyTracks/res/values-de/strings.xml +++ b/MyTracks/res/values-de/strings.xml @@ -26,52 +26,54 @@ limitations under the License. Alle Tracks löschen Einstellungen Höhe - Bitte wählen Sie einen Track zur Ansicht aus + Bitte wähle einen Track zur Ansicht aus Aktueller Track Letztes Segment + Sensorstatus + Aggregierte Statistiken - Es wurde kein \'Location Manager\' auf diesem Gerät gefunden. - Konnte keinen GPS-Empfänger auf diesem Gerät finden. - Beim Senden Ihres Tracks an Google My Maps ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal. - Beim Senden Ihres Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuchen Sie es später nochmal. - Aufzeichnen fehlgeschlagen. Bitte versuchen Sie es nochmal. - Anhalten unmöglich. Bitte versuchen Sie es nochmal. - Datei nicht lesbar. + Es wurde kein Dienst zur Positionsermittlung auf diesem Gerät gefunden. + Es wurde kein GPS-Empfänger auf diesem Gerät gefunden. + Beim Senden des Tracks an Google MyMaps ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. + Beim Senden des Tracks an Google Docs ist ein Fehler aufgetreten. Bitte versuche es später noch einmal. + Start der Aufzeichnung fehlgeschlagen. Bitte versuche es noch einmal. + Anhalten unmöglich. Bitte versuche es noch einmal. + Konnte Datei nicht lesen. Datei ist nicht im GPX-Format. - Ein Fehler ist aufgetreten. Bitte versuchen Sie es nochmal. + Ein Fehler ist aufgetreten. Bitte versuche es noch einmal. Track existiert nicht. - Die Datei ist zu groß (Obergrenze sind 500 KB). Operation abgebrochen. + Die Datei ist zu groß (maximal 500 KB). Operation abgebrochen. Der Wegpunkt kann nicht einfügt werden. Keine Positionsbestimmung möglich. - Warten auf ein besseres GPS-Signal. Bitte stellen Sie sicher, dass der Himmel sichtbar ist. + Warte auf ein besseres GPS-Signal. Bitte stelle sicher, dass der Himmel sichtbar ist. Ein neuer Track wird aufgezeichnet. - Die Aufzeichnung wurde beendet. + Die Aufzeichnung wurde angehalten. Eine neue Karte wurde auf maps.google.com erstellt. - Ihr Track wurde auf maps.google.com gespeichert. - Ihr Track wurde in einer neuen Tabelle bei docs.google.com gespeichert. - Ihr Track wurde an docs.google.com gesendet. + Dein Track wurde auf maps.google.com gespeichert. + Dein Track wurde in einer neuen Tabelle bei docs.google.com gespeichert. + Dein Track wurde an docs.google.com gesendet. Manchmal dauert es ein paar Minuten, bis neue Tracks bei maps.google.com erscheinen. - Berühren Sie hier um zu den Sicherheits- und Positionseinstellungen zu gelangen. Aktiviere \'GPS-Satelliten\'. Drücken Sie dann den Zurück-Knopf um zurückzukehren. - Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Funktürmen und WiFi-Netzwerken. - Ihre Position kann derzeit nicht bestimmt werden. Aktivieren Sie mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stellen Sie sicher, dass der Himmel sichtbar ist. + Hier berühren, um zu den Sicherheits- und Positionseinstellungen zu gelangen. Aktiviere \'GPS-Satelliten\'. Drücke dann den Zurück-Knopf, um hierher zurückzukehren. + Anmerkung: Eine ungefähre Position wird angezeigt, bestimmt mittels Mobilfunkstationen und WiFi-Netzwerken. + Die Position kann derzeit nicht bestimmt werden. Aktiviere mehr Positionsquellen (siehe Sicherheits- und Positionseinstellungen) und stelle sicher, dass der Himmel sichtbar ist. Ein Wegpunkt wurde eingefügt. Ein Statistikwegpunkt wurde eingefügt. Bitte warten… - Ihr GPS-Track wird an Google gesendet. - Lese Track - Bereite Track vor - Anmelden bei Google Maps - Anmelden bei Google Docs - Track an Google Docs senden - Track an Google Maps senden - Neue \'Meine Karte\' erstellen - Importiere GPS-Track. Das dauert ein wenig. - Schreibe Datei auf SD-Karte. Das dauert ein wenig. - Aktualisiere Trackstatistik + Dein Track wird an Google gesendet... + Lese Track... + Bereite Track vor... + Anmeldung bei Google Maps... + Anmeldung bei Google Docs... + Track wird an Google Docs gesendet... + Track wird an Google Maps gesendet... + Neue \'Meine Karte\' wird erstellt... + Importiere GPS-Track. Das kann ein wenig dauern. + Schreibe Datei auf SD-Karte. Das kann ein wenig dauern. + Aktualisiere Trackstatistik... Bist Du Dir wirklich sicher? @@ -80,17 +82,19 @@ limitations under the License. Dieser Wegpunkt wird endgültig von diesem Gerät gelöscht. + Erstellt von My Tracks für Android. + Erstellt von <a href=\'http://mytracks.appspot.com\'>My Tracks</a> für Android. Neue Karte Punkte Aufgezeichnet am - "Aktivität" - "Art des Wegpunktes" - "(Teil %d)" + Aktivität + Art des Wegpunktes + (Teil %d) - "Ihr Track wird aufgezeichnet…" + Dein Track wird aufgezeichnet… Nein @@ -100,7 +104,7 @@ limitations under the License. Wegpunkt - (Anfang) + (Beginn) (Ende) @@ -114,15 +118,22 @@ limitations under the License. Auf SD-Karte speichern… GPX-Datei exportieren KML-Datei exportieren + CSV-Datei exportieren + TCX-Datei exportieren An Google MyMaps senden Alle Tracks löschen - Eine Track auswählen. Lange berühren für mehr Optionen. - Alle Tracks: + + Alle Tracks exportieren + Exportieren beendet + Einen Track auswählen. Lange berühren für mehr Optionen. + Alle Tracks Dieser Track Mit Freunden teilen… Ein Link zur Google MyMaps-Karte Eine GPX-Datei Eine KML-Datei + Eine CSV-Datei + Eine TCX-Datei Karte zurücksetzen. @@ -150,64 +161,99 @@ limitations under the License. Einstellungen für My Tracks Anzeige - Aufzeichnen - Mit Freunden Teilen + Aufzeichnung + Mit Freunden teilen - Dauer zwischen Ansagen: - Kürzeste Zeit zwichen zwei Ansagen + In dieser Android-Version nicht verfügbar + + Dauer zwischen Ansagen + Kürzeste Zeit zwischen zwei Ansagen - Kleinste Entfernung zwischen Punkten: + Kleinste Entfernung zwischen Punkten Kleinste Entfernung zwischen zwei Positionsaufzeichnungen - Neues Segment wenn Punkte weiter entfernt als: - Der Track wird in Zwei geteilt wenn zwei folgende Punkte weiter entfernt sind als hier angegeben. + Neues Segment, wenn Punkte weiter entfernt als + Der Track wird in zwei geteilt, wenn zwei folgende Punkte weiter entfernt sind als hier angegeben. - Aufzeichnungsfrequenz: - Kürzeste Zeit zwischen zwei Positionsaufzeichnungen. + Aufzeichnungsfrequenz + Kürzeste Zeit zwischen zwei Positionsaufzeichnungen - Aufzeichnung pausieren wenn Genauigkeit kleiner als: - Die Aufzeichnung wird kurzfristig angehalten, wenn Ihre Position nicht genauer als hier angegeben ermittelt werden kann. + Aufzeichnung pausieren, wenn Genauigkeit kleiner als + Die Aufzeichnung wird kurzfristig angehalten, wenn Deine Position nicht genauer als hier angegeben ermittelt werden kann. Entfernungen in metrischen Einheiten anzeigen Alle Entfernungen werden in metrischen Einheiten angezeigt. - Alle Entfernungen werden in britischen Längeneinheiten angezeigt. + Alle Entfernungen werden in imperialen Längeneinheiten angezeigt. Karten auf maps.google.com veröffentlichen Hochgeladene Karten auf maps.google.com sind für alle sichtbar. Hochgeladene Karten auf maps.google.com werden nicht öffentlich sichtbar aufgelistet. - Geschwindigkeitsanzeige: + Geschwindigkeitsanzeige Geschwindigkeit wird angezeigt. - Gangart wird angezeigt. + Pace wird angezeigt. Nur URL als Einladung zum Teilen senden - Beim teilen mit Freunden wird nur die Karten URL gesendet. - Beim teilen mit Freunden wird der GPS Track selbst gesendet. + Beim Teilen mit Freunden wird nur die Karten-URL gesendet. + Beim Teilen mit Freunden wird der GPS-Track selbst gesendet. - Zerteilungs Frequenz - Längste Dauer oder Entfernung nach der automatisch ein Marker eingefügt wird. - Tempo + Zerteilungsfrequenz + Längste Dauer oder Entfernung, nach der automatisch ein Marker eingefügt wird. + + Zeitbegrenzung der Auto-Wiederaufnahme + Maximale Zeit in Minuten, nach der eine abgebrochene Aufzeichnung nach einem Telefonneustart wiederaufgenommen wird. + + Zeitstempel als Trackname + Der Trackname wird aus Datum und Zeit zum Aufzeichnungsbeginn zusammengesetzt. + Tracknamen erhalten eine fortlaufende Nummer. + + Pace + + Wird nur unter Android 2.0 oder höher unterstützt + Sensortyp + Der Typ des zu verbindenden Sensors + Sensor-Id + Die zum Sensor zu sendende Id + Die Id muss eine Ganzzahl sein. + Bluetooth-Sensor + Der zu verbindende Bluetoothsensor + Art der Datenanzeige + + Datensicherung (BETA) + SD-Karten-Sicherung + Jetzt sichern + Eine Kopie aller Deiner Tracks und Einstellungen auf die SD-Karte schreiben + Jetzt wiederherstellen + Alle Tracks und Einstellungen von der SD-Karte holen. + Während ein Track aufgezeichnet wird, kann nicht mit der Datensicherung gearbeitet werden. + + + Eine Wiederherstellung von der SD-Karte ÜBERSCHREIBT alle vorhandenen Tracks und Einstellungen. Willst Du das wirklich? + Wähle die wiederherzustellende Datensicherung aus + Keine Datensicherungen gefunden. + Schreibe alle Tracks und Einstellungen auf die SD-Karte. Dies kann eine Weile dauern... + Lese alle Tracks und Einstellungen von der SD-Karte. Dies kann eine Weile dauern... GPS-Satelliten - Wähle eine Karte von Deinem Konto aus: + Wähle eine Karte von Deinem Konto aus Neue Karte erstellen… An Google My Maps senden Karten werden geladen… - Ihre Karten konnten im Augenblick nicht geladen werden! + Deine Karten konnten im Augenblick nicht geladen werden! Diese Karte auswählen Kartendetails bearbeiten Karte mit Freunden teilen Track mit Freunden teilen Karte vom Konto löschen - Kartentitel: - Trackbeschreibung: - Diese Karte und Ihren Inhalt in Suchergebnissen sichtbar machen. - Diese Karte und Ihren Inhalt nicht in Suchergebnissen sichtbar machen. - Diese Karte und Ihr Inhalt sind in Suchergebnissen sichtbar. - Weder diese Karte noch Ihr Inhalt sind in Suchergebnissen sichtbar, allerdings können Sie trotzdem von anderen durch einen direkten Link angezeigt werden. + Kartentitel + Trackbeschreibung + Diese Karte und ihren Inhalt in Suchergebnissen sichtbar machen. + Diese Karte und ihren Inhalt nicht in Suchergebnissen sichtbar machen. + Diese Karte und ihr Inhalt sind in Suchergebnissen sichtbar. + Weder diese Karte noch ihr Inhalt sind in Suchergebnissen sichtbar, allerdings kann sie trotzdem von anderen durch einen direkten Link angezeigt werden. Schließen Bearbeiten Abbrechen @@ -222,6 +268,8 @@ limitations under the License. Vielleicht interessierst Du Dich für diese Track: %s. + + Öffentlich Nicht in Suchergebnissen enthalten. Du hast noch keine Karten. @@ -229,11 +277,11 @@ limitations under the License. Durchschnitt in Bew. - Durchschnitt in Bew. + Pace in Bew. Durchschnittsgeschw. - Durchschnittstempo + Durchschnitts-Pace Höhenprofil Höhengewinn @@ -244,7 +292,7 @@ limitations under the License. Niedrigste Höhenlage Minimale Steigung - Schnellstes Tempo + Minimaler Pace Höchste Höhenlage @@ -254,14 +302,14 @@ limitations under the License. Lade Track… - Diagram Einstellungen + Diagramm-Einstellungen - X Achse + X-Achse Nach Entfernung Nach Zeit - Y Achse + Y-Achse @@ -282,7 +330,7 @@ limitations under the License. Kilometer pro Stunde pro Kilometer Meilen pro Stunde - pro meile + pro Meile Zeit in Bewegung @@ -290,10 +338,14 @@ limitations under the License. Zeit SD-Karte nicht verfügbar Geschwindigkeit - Sie müssen erst eine Strecke zurücklegen. + Du musst erst eine Aufzeichnung beginnen. Statistik Gesamtentfernung Entfernung + Energie + Kadenz + Herzfrequenz + Höchste Herzfrequenz Bitte warten… @@ -316,22 +368,24 @@ limitations under the License. Kein Konto vorhanden - Kein Konto gefunden um Tracks zu versenden. - Wählen Sei ein Konto + Kein Konto gefunden, um Tracks zu versenden. + Wähle ein Konto - Keine externes Speichermedium verfügbar. Stellen Sie sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird. + Keine externes Speichermedium verfügbar. Stelle sicher, dass eine SD-Karte vorhanden ist und nicht bereits von einem Computer darauf zugegriffen wird. Die Datei kann nicht geschrieben werden. + Die Datei konnte nicht gelesen werden. Das Verzeichnis auf der SD-Karte kann nicht erstellt werden. Der Track wurde auf die SD-Karte geschrieben. + Lesen von der SD-Karte beendet. Suche nach neuer Version Jetzt aktualisieren - Fortfahren ohne zu aktualisieren - Ihre Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisieren Sie %1$s. - Ihre Version von %1$s funktioniert möglicherweise nicht mehr richtig. Sie können sie jetzt aktualisieren, oder auf sie auf eigene Gefahr weiterverwenden. Einige Funktionen wie Datenaustausch mit Google-Diensten wird möglicherweise nicht mehr funktionieren, solange Sie die Software nicht aktualisieren. - Eine neue Version von %1$s ist jetzt verfügbar. Sie können Ihre aktuelle Version ohne Probleme weiterverwenden, aber um die neuesten Funktionen in Anspruch nehmen zu können, müssen sie die Software aktualisieren. + Fortfahren, ohne zu aktualisieren + Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig und wird nicht mehr unterstützt. Bitte aktualisiere %1$s. + Deine Version von %1$s funktioniert möglicherweise nicht mehr richtig. Du kannst sie jetzt aktualisieren, oder auf sie auf eigene Gefahr weiterverwenden. Einige Funktionen wie Datenaustausch mit Google-Diensten werden möglicherweise nicht mehr funktionieren, solange Du die Software nicht aktualisierst. + Eine neue Version von %1$s ist jetzt verfügbar. Du kannst Deine installierte Version ohne Probleme weiterverwenden, aber um die neuesten Funktionen in Anspruch nehmen zu können, musst Du die Software aktualisieren. Willkommen @@ -348,4 +402,40 @@ limitations under the License. $6 : Speed units (mph) : Pace units (per mile) --> My Tracks %1$s %2$.2f %3$s in %4$s mit %5$s %6$s + + + Einheitenbenachrichtigung + Voreingestellt sind metrische Einheiten. Willst Du diese Einstellung ändern? + + + Bluetootheinstellungen + Geräteverbindung + Öffnet Androids Bluetootheinstellungen, um Geräte zu verbinden. + Sensoreinstellungen + + + Verbunden mit %s + Verbinde mit Bluetoothsensor ... + Bluetoothsensor nicht verbunden. + + + Sensor Data Time + Energiemessung aktiviert + Herzfrequenzmessung aktiviert + Keine Sensoren verfügbar + Aktiviert + Verbinde... + Verbunden + Getrennt + Kein Sensor-Dienst + Suche... + Sende... + + + Track-Aufzeichnungs-Benachrichtigungen empfangen + Aufgezeichnete Trackdaten lesen + Aufgezeichnete Trackdaten schreiben, Starten/Anhalten des Aufzeichnens von Tracks + Werde benachrichtigt, wenn die Aufzeichnung eines Tracks beginnt oder anhält. + Ermöglicht das Lesen von Trackdaten wie Messpunkten oder Wegpunkten. + Ermöglicht das Schreiben von Trackdaten wie beim Starten der Aufzeichnung oder beim Hinzufügen von Wegpunkten. diff --git a/MyTracks/res/values/arrays.xml b/MyTracks/res/values/arrays.xml index 3eb41e8fe..964befc7d 100644 --- a/MyTracks/res/values/arrays.xml +++ b/MyTracks/res/values/arrays.xml @@ -393,12 +393,16 @@ limitations under the License. None + ANT+ Sensors + SRM ANT+ Bridge Zephyr Bluetooth @string/none + @string/ant_sensor_type + @string/srm_ant_bridge_sensor_type @string/zephyr_sensor_type diff --git a/MyTracks/res/values/do_not_translate.xml b/MyTracks/res/values/do_not_translate.xml new file mode 100644 index 000000000..32095d881 --- /dev/null +++ b/MyTracks/res/values/do_not_translate.xml @@ -0,0 +1,36 @@ + + + + + My Tracks + + + mytracks@googlegroups.com + mytracks.appspot.com + Copyright 2009 Google Inc. + Bartlomiej Niechwiej + Dylan Casey + Leif Hendrik Wilden + Rodrigo Damazio Bovendorp + Sandor Dornbush + + + NONE + ANT + SRM_ANT_BRIDGE + ZEPHYR + diff --git a/MyTracks/res/values/keys.xml b/MyTracks/res/values/keys.xml index ebf7cdf07..b8e43f668 100644 --- a/MyTracks/res/values/keys.xml +++ b/MyTracks/res/values/keys.xml @@ -17,6 +17,9 @@ limitations under the License. announcementFrequency + antHeartRateSensorId + antPowerSensorId + antSRMBridgeSensorId autoResumeTrackTimeout bluetoothDevice bluetoothPairing diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index c2d8886e9..487afee53 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -15,9 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - My Tracks - Record track Stop recording @@ -216,15 +213,15 @@ limitations under the License. Only supported in Android 2.0 or above Sensor Type - The type of sensor to connect to. - Sensor Id - The Id sent to sensors. - The Id must be an integer value. + The type of sensor to connect to + Clear ANT heart rate sensor pairing + Clear ANT power sensor pairing + Clear ANT srm bridge sensor pairing + ANT sensor not paired + ANT sensor paired with: %d Bluetooth sensor - The bluetooth sensor to connect to. + The bluetooth sensor to connect to Modular Stats Display - The stats screen is split in various modes. - Display all stats on one screen Backup (BETA) SD Card Backup @@ -384,16 +381,6 @@ limitations under the License. Support Support: - - mytracks@googlegroups.com - mytracks.appspot.com - Copyright 2009 Google Inc. - Bartlomiej Niechwiej - Dylan Casey - Leif Hendrik Wilden - Rodrigo Damazio Bovendorp - Sandor Dornbush - Send to... Google My Maps @@ -454,10 +441,6 @@ limitations under the License. bluetoothSensorsEnabled Sensor Options - - NONE - ZEPHYR - Connected to %s Bluetooth sensor connecting ... diff --git a/MyTracks/res/xml/preferences.xml b/MyTracks/res/xml/preferences.xml index c0b519386..7e9ec97c9 100644 --- a/MyTracks/res/xml/preferences.xml +++ b/MyTracks/res/xml/preferences.xml @@ -122,6 +122,18 @@ limitations under the License. android:summary="@string/bluetooth_pairing_summary" android:persistent="false" /> + + + + + + diff --git a/MyTracks/src/com/dsi/ant/AntDefine.java b/MyTracks/src/com/dsi/ant/AntDefine.java new file mode 100644 index 000000000..4aabb7eb2 --- /dev/null +++ b/MyTracks/src/com/dsi/ant/AntDefine.java @@ -0,0 +1,276 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + + + +/** + * The Android Ant API is not finalized, and *will* change. Use at your + * own risk. + * + * Public API for controlling the Ant Service. + * AntDefines contains definitions commonly used in ANT messaging. + * + * @hide + */ +public class AntDefine { + + ////////////////////////////////////////////// + // Library Version Information + ////////////////////////////////////////////// + public static final int ANT_LIBRARY_VERSION_CODE = 5; + public static final int ANT_LIBRARY_VERSION_MAJOR = 1; + public static final int ANT_LIBRARY_VERSION_MINOR = 4; + public static final String ANT_LIBRARY_VERSION_NAME = String.valueOf(ANT_LIBRARY_VERSION_MAJOR) + "." + String.valueOf(ANT_LIBRARY_VERSION_MINOR); + + ////////////////////////////////////////////// + // Valid Configuration Values + ////////////////////////////////////////////// + public static final byte MIN_BIN = 0; + public static final byte MAX_BIN = 10; + + public static final short MIN_DEVICE_ID = 0; + public static final short MAX_DEVICE_ID = (short)65535; + + public static final short MIN_BUFFER_THRESHOLD = 0; + public static final short MAX_BUFFER_THRESHOLD = 996; + + ////////////////////////////////////////////// + // ANT Message Payload Size + ////////////////////////////////////////////// + public static final byte ANT_STANDARD_DATA_PAYLOAD_SIZE =((byte)8); + + ////////////////////////////////////////////// + // ANT LIBRARY Extended Data Message Fields + // NOTE: You must check the extended message + // bitfield first to find out which fields + // are present before accessing them! + ////////////////////////////////////////////// + public static final byte ANT_EXT_MESG_DEVICE_ID_FIELD_SIZE =((byte)4); + //public static final byte ANT_EXT_STRING_SIZE =((byte)19); // this is the additional buffer space required required for setting USB Descriptor strings + public static final byte ANT_EXT_STRING_SIZE =((byte)0); // changed to 0 as we will not be dealing with ANT USB parts. + + ////////////////////////////////////////////// + // ANT Extended Data Message Bifield Definitions + ////////////////////////////////////////////// + public static final byte ANT_EXT_MESG_BITFIELD_DEVICE_ID =((byte)0x80); // first field after bitfield + public static final byte ANT_EXT_MESG_BITFIELD_RSSI =((byte)0x40); // next field after ID, if there is one + public static final byte ANT_EXT_MESG_BITFIELD_TIME_STAMP =((byte)0x20); // next field after RSSI, if there is one + + // 4 bits free reserved set to 0 + public static final byte ANT_EXT_MESG_BIFIELD_EXTENSION =((byte)0x01); + + // extended message input bitfield defines + public static final byte ANT_EXT_MESG_BITFIELD_OVERWRITE_SHARED_ADR =((byte)0x10); + public static final byte ANT_EXT_MESG_BITFIELD_TRANSMISSION_TYPE =((byte)0x08); + + ////////////////////////////////////////////// + // ID Definitions + ////////////////////////////////////////////// + public static final byte ANT_ID_SIZE =((byte)4); + public static final byte ANT_ID_TRANS_TYPE_OFFSET =((byte)3); + public static final byte ANT_ID_DEVICE_TYPE_OFFSET =((byte)2); + public static final byte ANT_ID_DEVICE_NUMBER_HIGH_OFFSET =((byte)1); + public static final byte ANT_ID_DEVICE_NUMBER_LOW_OFFSET =((byte)0); + public static final byte ANT_ID_DEVICE_TYPE_PAIRING_FLAG =((byte)0x80); + + public static final byte ANT_TRANS_TYPE_SHARED_ADDR_MASK =((byte)0x03); + public static final byte ANT_TRANS_TYPE_1_BYTE_SHARED_ADDRESS =((byte)0x02); + public static final byte ANT_TRANS_TYPE_2_BYTE_SHARED_ADDRESS =((byte)0x03); + + ////////////////////////////////////////////// + // Assign Channel Parameters + ////////////////////////////////////////////// + public static final byte PARAMETER_RX_NOT_TX =((byte)0x00); + public static final byte PARAMETER_TX_NOT_RX =((byte)0x10); + public static final byte PARAMETER_SHARED_CHANNEL =((byte)0x20); + public static final byte PARAMETER_NO_TX_GUARD_BAND =((byte)0x40); + public static final byte PARAMETER_ALWAYS_RX_WILD_CARD_SEARCH_ID =((byte)0x40); //Pre-AP2 + public static final byte PARAMETER_RX_ONLY =((byte)0x40); + + ////////////////////////////////////////////// + // Ext. Assign Channel Parameters + ////////////////////////////////////////////// + public static final byte EXT_PARAM_ALWAYS_SEARCH =((byte)0x01); + public static final byte EXT_PARAM_FREQUENCY_AGILITY =((byte)0x04); + + ////////////////////////////////////////////// + // Radio TX Power Definitions + ////////////////////////////////////////////// + public static final byte RADIO_TX_POWER_LVL_MASK =((byte)0x03); + + public static final byte RADIO_TX_POWER_LVL_0 =((byte)0x00); //(formerly: RADIO_TX_POWER_MINUS20DB); lowest + public static final byte RADIO_TX_POWER_LVL_1 =((byte)0x01); //(formerly: RADIO_TX_POWER_MINUS10DB); + public static final byte RADIO_TX_POWER_LVL_2 =((byte)0x02); //(formerly: RADIO_TX_POWER_MINUS5DB); + public static final byte RADIO_TX_POWER_LVL_3 =((byte)0x03); //(formerly: RADIO_TX_POWER_0DB); highest + + ////////////////////////////////////////////// + // Channel Status + ////////////////////////////////////////////// + public static final byte STATUS_CHANNEL_STATE_MASK =((byte)0x03); + public static final byte STATUS_UNASSIGNED_CHANNEL =((byte)0x00); + public static final byte STATUS_ASSIGNED_CHANNEL =((byte)0x01); + public static final byte STATUS_SEARCHING_CHANNEL =((byte)0x02); + public static final byte STATUS_TRACKING_CHANNEL =((byte)0x03); + + ////////////////////////////////////////////// + // Standard capabilities defines + ////////////////////////////////////////////// + public static final byte CAPABILITIES_NO_RX_CHANNELS =((byte)0x01); + public static final byte CAPABILITIES_NO_TX_CHANNELS =((byte)0x02); + public static final byte CAPABILITIES_NO_RX_MESSAGES =((byte)0x04); + public static final byte CAPABILITIES_NO_TX_MESSAGES =((byte)0x08); + public static final byte CAPABILITIES_NO_ACKD_MESSAGES =((byte)0x10); + public static final byte CAPABILITIES_NO_BURST_TRANSFER =((byte)0x20); + + ////////////////////////////////////////////// + // Advanced capabilities defines + ////////////////////////////////////////////// + public static final byte CAPABILITIES_OVERUN_UNDERRUN =((byte)0x01); // Support for this functionality has been dropped + public static final byte CAPABILITIES_NETWORK_ENABLED =((byte)0x02); + public static final byte CAPABILITIES_AP1_VERSION_2 =((byte)0x04); // This Version of the AP1 does not support transmit and only had a limited release + public static final byte CAPABILITIES_SERIAL_NUMBER_ENABLED =((byte)0x08); + public static final byte CAPABILITIES_PER_CHANNEL_TX_POWER_ENABLED =((byte)0x10); + public static final byte CAPABILITIES_LOW_PRIORITY_SEARCH_ENABLED =((byte)0x20); + public static final byte CAPABILITIES_SCRIPT_ENABLED =((byte)0x40); + public static final byte CAPABILITIES_SEARCH_LIST_ENABLED =((byte)0x80); + + ////////////////////////////////////////////// + // Advanced capabilities 2 defines + ////////////////////////////////////////////// + public static final byte CAPABILITIES_LED_ENABLED =((byte)0x01); + public static final byte CAPABILITIES_EXT_MESSAGE_ENABLED =((byte)0x02); + public static final byte CAPABILITIES_SCAN_MODE_ENABLED =((byte)0x04); + public static final byte CAPABILITIES_RESERVED =((byte)0x08); + public static final byte CAPABILITIES_PROX_SEARCH_ENABLED =((byte)0x10); + public static final byte CAPABILITIES_EXT_ASSIGN_ENABLED =((byte)0x20); + public static final byte CAPABILITIES_FREE_1 =((byte)0x40); + public static final byte CAPABILITIES_FIT1_ENABLED =((byte)0x80); + + ////////////////////////////////////////////// + // Advanced capabilities 3 defines + ////////////////////////////////////////////// + public static final byte CAPABILITIES_SENSRCORE_ENABLED =((byte)0x01); + public static final byte CAPABILITIES_RESERVED_1 =((byte)0x02); + public static final byte CAPABILITIES_RESERVED_2 =((byte)0x04); + public static final byte CAPABILITIES_RESERVED_3 =((byte)0x08); + + + ////////////////////////////////////////////// + // Burst Message Sequence + ////////////////////////////////////////////// + public static final byte CHANNEL_NUMBER_MASK =((byte)0x1F); + public static final byte SEQUENCE_NUMBER_MASK =((byte)0xE0); + public static final byte SEQUENCE_NUMBER_ROLLOVER =((byte)0x60); + public static final byte SEQUENCE_FIRST_MESSAGE =((byte)0x00); + public static final byte SEQUENCE_LAST_MESSAGE =((byte)0x80); + public static final byte SEQUENCE_NUMBER_INC =((byte)0x20); + + ////////////////////////////////////////////// + // Control Message Flags + ////////////////////////////////////////////// + public static final byte BROADCAST_CONTROL_BYTE =((byte)0x00); + public static final byte ACKNOWLEDGED_CONTROL_BYTE =((byte)0xA0); + + ////////////////////////////////////////////// + // Response / Event Codes + ////////////////////////////////////////////// + public static final byte RESPONSE_NO_ERROR =((byte)0x00); + public static final byte NO_EVENT =((byte)0x00); + + public static final byte EVENT_RX_SEARCH_TIMEOUT =((byte)0x01); + public static final byte EVENT_RX_FAIL =((byte)0x02); + public static final byte EVENT_TX =((byte)0x03); + public static final byte EVENT_TRANSFER_RX_FAILED =((byte)0x04); + public static final byte EVENT_TRANSFER_TX_COMPLETED =((byte)0x05); + public static final byte EVENT_TRANSFER_TX_FAILED =((byte)0x06); + public static final byte EVENT_CHANNEL_CLOSED =((byte)0x07); + public static final byte EVENT_RX_FAIL_GO_TO_SEARCH =((byte)0x08); + public static final byte EVENT_CHANNEL_COLLISION =((byte)0x09); + public static final byte EVENT_TRANSFER_TX_START =((byte)0x0A); // a pending transmit transfer has begun + + public static final byte EVENT_CHANNEL_ACTIVE =((byte)0x0F); + + public static final byte EVENT_TRANSFER_TX_NEXT_MESSAGE =((byte)0x11); // only enabled in FIT1 + + public static final byte CHANNEL_IN_WRONG_STATE =((byte)0x15); // returned on attempt to perform an action from the wrong channel state + public static final byte CHANNEL_NOT_OPENED =((byte)0x16); // returned on attempt to communicate on a channel that is not open + public static final byte CHANNEL_ID_NOT_SET =((byte)0x18); // returned on attempt to open a channel without setting the channel ID + public static final byte CLOSE_ALL_CHANNELS =((byte)0x19); // returned when attempting to start scanning mode, when channels are still open + + public static final byte TRANSFER_IN_PROGRESS =((byte)0x1F); // returned on attempt to communicate on a channel with a TX transfer in progress + public static final byte TRANSFER_SEQUENCE_NUMBER_ERROR =((byte)0x20); // returned when sequence number is out of order on a Burst transfer + public static final byte TRANSFER_IN_ERROR =((byte)0x21); + public static final byte TRANSFER_BUSY =((byte)0x22); + + public static final byte INVALID_MESSAGE_CRC =((byte)0x26); // returned if there is a framing error on an incomming message + public static final byte MESSAGE_SIZE_EXCEEDS_LIMIT =((byte)0x27); // returned if a data message is provided that is too large + public static final byte INVALID_MESSAGE =((byte)0x28); // returned when the message has an invalid parameter + public static final byte INVALID_NETWORK_NUMBER =((byte)0x29); // returned when an invalid network number is provided + public static final byte INVALID_LIST_ID =((byte)0x30); // returned when the provided list ID or size exceeds the limit + public static final byte INVALID_SCAN_TX_CHANNEL =((byte)0x31); // returned when attempting to transmit on channel 0 when in scan mode + + public static final byte INVALID_PARAMETER_PROVIDED =((byte)0x33); // returned when an invalid parameter is specified in a configuration message + + public static final byte EVENT_SERIAL_QUE_OVERFLOW =((byte)0x34); + public static final byte EVENT_QUE_OVERFLOW =((byte)0x35); // ANT event que has overflowed and lost 1 or more events + + public static final byte EVENT_CLK_ERROR =((byte)0x36); // debug event for XOSC16M on LE1 + + public static final byte SCRIPT_FULL_ERROR =((byte)0x40); // error writing to script, memory is full + public static final byte SCRIPT_WRITE_ERROR =((byte)0x41); // error writing to script, bytes not written correctly + public static final byte SCRIPT_INVALID_PAGE_ERROR =((byte)0x42); // error accessing script page + public static final byte SCRIPT_LOCKED_ERROR =((byte)0x43); // the scripts are locked and can't be dumped + + public static final byte NO_RESPONSE_MESSAGE =((byte)0x50); // returned to the Command_SerialMessageProcess function, so no reply message is generated + public static final byte RETURN_TO_MFG =((byte)0x51); // default return to any mesg when the module determines that the mfg procedure has not been fully completed + + public static final byte FIT_ACTIVE_SEARCH_TIMEOUT =((byte)0x60); // Fit1 only event added for timeout of the pairing state after the Fit module becomes active + public static final byte FIT_WATCH_PAIR =((byte)0x61); // Fit1 only + public static final byte FIT_WATCH_UNPAIR =((byte)0x62); // Fit1 only + + public static final byte USB_STRING_WRITE_FAIL =((byte)0x70); + + // Internal only events below this point + public static final byte INTERNAL_ONLY_EVENTS =((byte)0x80); + public static final byte EVENT_RX =((byte)0x80); // INTERNAL: Event for a receive message + public static final byte EVENT_NEW_CHANNEL =((byte)0x81); // INTERNAL: EVENT for a new active channel + public static final byte EVENT_PASS_THRU =((byte)0x82); // INTERNAL: Event to allow an upper stack events to pass through lower stacks + + public static final byte EVENT_BLOCKED =((byte)0xFF); // INTERNAL: Event to replace any event we do not wish to go out, will also zero the size of the Tx message + + /////////////////////////////////////////////////////// + // Script Command Codes + /////////////////////////////////////////////////////// + public static final byte SCRIPT_CMD_FORMAT =((byte)0x00); + public static final byte SCRIPT_CMD_DUMP =((byte)0x01); + public static final byte SCRIPT_CMD_SET_DEFAULT_SECTOR =((byte)0x02); + public static final byte SCRIPT_CMD_END_SECTOR =((byte)0x03); + public static final byte SCRIPT_CMD_END_DUMP =((byte)0x04); + public static final byte SCRIPT_CMD_LOCK =((byte)0x05); + + /////////////////////////////////////////////////////// + // Reset Mesg Codes + /////////////////////////////////////////////////////// + public static final byte RESET_FLAGS_MASK =((byte)0xE0); + public static final byte RESET_SUSPEND =((byte)0x80); // this must follow bitfield def + public static final byte RESET_SYNC =((byte)0x40); // this must follow bitfield def + public static final byte RESET_CMD =((byte)0x20); // this must follow bitfield def + public static final byte RESET_WDT =((byte)0x02); + public static final byte RESET_RST =((byte)0x01); + public static final byte RESET_POR =((byte)0x00); + +} \ No newline at end of file diff --git a/MyTracks/src/com/dsi/ant/AntInterface.java b/MyTracks/src/com/dsi/ant/AntInterface.java new file mode 100644 index 000000000..20d30349a --- /dev/null +++ b/MyTracks/src/com/dsi/ant/AntInterface.java @@ -0,0 +1,1134 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + + +// TODO: Auto-generated Javadoc + +/** + * Public API for controlling the Ant Service. AntInterface is a proxy + * object for controlling the Ant Service via IPC. Creating a AntInterface + * object will create a binding with the Ant service. + * + * @hide + */ +public class AntInterface { + + /** The Log Tag. */ + public static final String TAG = "ANTInterface"; + + /** Enable debug logging. */ + public static boolean DEBUG = false; + + /** Search string to find ANT Radio Proxy Service in the Android Marketplace */ + private static final String MARKET_SEARCH_TEXT_DEFAULT = "ANT Radio Service Dynastream Innovations Inc"; + + /** Inter-process communication with the ANT Radio Proxy Service. */ + public static IAnt sAntReceiver = null; + public static IServiceSettings sServiceSettingsReceiver = null; + + /** Singleton instance of this class. */ + private static AntInterface INSTANCE; + + /** Used when obtaining a reference to the singleton instance. */ + private static Object INSTANCE_LOCK = new Object(); + + /** The context to use. */ + private static Context sContext = null; + + /** Listens to changes to service connection status. */ + private static ServiceListener sServiceListener; + + /** Is the ANT Radio Proxy Service connected. */ + private static boolean sServiceConnected = false; + private static boolean sServiceSettingsConnected = false; + + private static int mServiceLibraryVersionCode = 0; + + /** + * An interface for notifying AntInterface IPC clients when they have + * been connected to the ANT service. + * + * @see ServiceEvent + */ + public interface ServiceListener + { + /** + * Called to notify the client when this proxy object has been + * connected to the ANT service. Clients must wait for + * this callback before making IPC calls on the ANT + * service. + */ + public void onServiceConnected(); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the ANT service. Clients must not + * make IPC calls on the ANT service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothAg service, but may be called more often in future. + */ + public void onServiceDisconnected(); + } + + + //Constructor + /** + * Instantiates a new ant interface. + * + * @param context the context + * @param listener the listener + * @since 1.0 + */ + private AntInterface(Context context, ServiceListener listener) + { + // This will be around as long as this process is + sContext = context; + sServiceListener = listener; + } + + /** + * Gets the single instance of AntInterface, creating it if it doesn't exist. + * + * @param context the context. + * @param listener the listener to be notified of status changes. + * @return the AntInterface instance. + * @since 1.0 + */ + public static AntInterface getInstance(Context context,ServiceListener listener) + { + if(DEBUG) Log.d(TAG, "getInstance"); + + synchronized (INSTANCE_LOCK) + { + if (INSTANCE == null) + { + if(DEBUG) Log.d(TAG, "getInstance: Creating new instance"); + + // TODO: rohan: bug, each new request for an instance will not have context and listener set to the requested objects + INSTANCE = new AntInterface(context,listener); + } + else + { + if(DEBUG) Log.d(TAG, "getInstance: Using existing instance"); + } + + if(!sServiceConnected) + { + if(DEBUG) Log.d(TAG, "getInstance: No connection to proxy service, attempting connection"); + + if(!INSTANCE.initService()) + { + Log.e(TAG, "getInstance: No connection to proxy service"); + + INSTANCE.destroy(); + INSTANCE = null; + } + } + + return INSTANCE; + } + } + + /** + * Go to market. + * + * @param pContext the context + * @param pSearchText the search text + * @since 1.2 + */ + public static void goToMarket(Context pContext, String pSearchText) + { + if(null == pSearchText) + { + goToMarket(pContext); + } + else + { + if(DEBUG) Log.i(TAG, "goToMarket: Search text = "+ pSearchText); + + Intent goToMarket = null; + goToMarket = new Intent(Intent.ACTION_VIEW,Uri.parse("http://market.android.com/search?q=" + pSearchText)); + goToMarket.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + pContext.startActivity(goToMarket); + } + } + + /** + * Go to market. + * + * @param pContext the context + * @since 1.2 + */ + public static void goToMarket(Context pContext) + { + if(DEBUG) Log.d(TAG, "goToMarket"); + + goToMarket(pContext, MARKET_SEARCH_TEXT_DEFAULT); + } + + /** + * Class for interacting with the ANT interface. + */ + private static ServiceConnection sIAntConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName pClassName, IBinder pService) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. We are communicating with our + // service through an IDL interface, so get a client-side + // representation of that from the raw service object. + if(DEBUG) Log.d(TAG, "sIAntConnection onServiceConnected()"); + sAntReceiver = IAnt.Stub.asInterface(pService); + + sServiceConnected = true; + + try + { + mServiceLibraryVersionCode = sAntReceiver.getServiceLibraryVersionCode(); + } + catch(RemoteException e) + { + Log.e(TAG, "sIAntConnection onServiceConnected: Could not get service library version"); + } + + switch(mServiceLibraryVersionCode) + { + // Fall through from newer versions + case 5: + { + if(!sServiceSettingsConnected) + { + boolean boundServiceSettings = sContext.bindService(new Intent(IServiceSettings.class.getName()), sIServiceSettingsConnection, Context.BIND_AUTO_CREATE); + Log.i(TAG, "sIAntConnection onServiceConnected: Bound with ANT Service Settings: " + boundServiceSettings); + } + else + { + if(DEBUG) Log.d(TAG, "sIAntConnection onServiceConnected: Already initialised Service Settings connection"); + } + } + } + + // Notify the attached application if it is registered + if (sServiceListener != null) + { + sServiceListener.onServiceConnected(); + } + else + { + if(DEBUG) Log.d(TAG, "sIAntConnection onServiceConnected: No service listener registered"); + } + } + + public void onServiceDisconnected(ComponentName pClassName) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + if(DEBUG) Log.d(TAG, "sIAntConnection onServiceDisconnected()"); + sAntReceiver = null; + + sServiceConnected = false; + + // Notify the attached application if it is registered + if (sServiceListener != null) + { + sServiceListener.onServiceDisconnected(); + } + else + { + if(DEBUG) Log.d(TAG, "sIAntConnection onServiceDisconnected: No service listener registered"); + } + + // Try and rebind to the service + INSTANCE.releaseService(); + INSTANCE.initService(); + } + }; + + /** + * Class for interacting with the ANT interface through the ANTLib version 5 functions. + */ + private static ServiceConnection sIServiceSettingsConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName pClassName, IBinder pService) { + // This is called when the connection with the service has been + // established, giving us the service object we can use to + // interact with the service. We are communicating with our + // service through an IDL interface, so get a client-side + // representation of that from the raw service object. + if(DEBUG) Log.d(TAG, "sIServiceSettingsConnection onServiceConnected()"); + sServiceSettingsReceiver = IServiceSettings.Stub.asInterface(pService); + + sServiceSettingsConnected = true; + } + + public void onServiceDisconnected(ComponentName pClassName) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + if(DEBUG) Log.d(TAG, "sIServiceSettingsConnection onServiceDisconnected()"); + sServiceSettingsReceiver = null; + + sServiceSettingsConnected = false; + + // Try and rebind to the service + INSTANCE.releaseService(); + INSTANCE.initService(); + } + }; + + + /** + * Binds this activity to the ANT service. + * + * @return true, if successful + */ + private boolean initService() { + if(DEBUG) Log.d(TAG, "initService() entered"); + + boolean ret = false; + + if(!sServiceConnected) + { + ret = sContext.bindService(new Intent(IAnt.class.getName()), sIAntConnection, Context.BIND_AUTO_CREATE); + Log.i(TAG, "initService(): Bound with ANT service: " + ret); + } + else + { + if(DEBUG) Log.d(TAG, "initService: already initialised service"); + ret = true; + } + + return ret; + } + + /** Unbinds this activity from the ANT service. */ + private void releaseService() { + if(DEBUG) Log.d(TAG, "releaseService() entered"); + + if(sServiceConnected) + { + sContext.unbindService(sIAntConnection); + sServiceConnected = false; + } + + if(sServiceSettingsConnected) + { + sContext.unbindService(sIServiceSettingsConnection); + sServiceSettingsConnected = false; + } + + if(DEBUG) Log.d(TAG, "releaseService() unbound."); + } + + /** + * True if this activity can communicate with the ANT service. + * + * @return true, if is service connected + * @since 1.2 + */ + public boolean isServiceConnected() + { + return sServiceConnected; + } + + /** + * Destroy. + * + * @return true, if successful + * @since 1.0 + */ + public boolean destroy() + { + if(DEBUG) Log.d(TAG, "destroy"); + + releaseService(); + + INSTANCE = null; + + return true; + } + + + /** + * Ant service connection lost. + */ + private void antServiceConnectionLost() + { + Log.e(TAG, "Connection to ANT service lost"); + } + + /** + * Ant service connection to settings binder lost. + */ + private void antServiceSettingsConnectionLost() + { + Log.e(TAG, "Connection to ANT service settings lost"); + } + + + ////------------------------------------------------- + + /** + * Enable. + * + * @return true, if successful + * @since 1.0 + */ + public boolean enable() + { + if(DEBUG) Log.d(TAG, "enable"); + + if(!sServiceConnected) + { + // Haven't received 'onConnected' notification yet + return false; + } + + boolean result = false; + try { + result = sAntReceiver.enable(); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * Disable. + * + * @return true, if successful + * @since 1.0 + */ + public boolean disable() + { + if(DEBUG) Log.d(TAG, "disable"); + + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.disable(); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * Checks if is enabled. + * + * @return true, if is enabled + * @since 1.0 + */ + public boolean isEnabled() + { + if(DEBUG) Log.d(TAG, "isEnabled"); + + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.isEnabled(); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT tx message. + * + * @param message the message + * @return true, if successful + * @since 1.0 + */ + public boolean ANTTxMessage(byte[] message) + { + if(DEBUG) Log.d(TAG, "ANTTxMessage"); + + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTTxMessage(message); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT reset system. + * + * @return true, if successful + * @since 1.0 + */ + public boolean ANTResetSystem() + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTResetSystem(); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT unassign channel. + * + * @param channelNumber the channel number + * @return true, if successful + * @since 1.0 + */ + public boolean ANTUnassignChannel(byte channelNumber) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTUnassignChannel(channelNumber); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT assign channel. + * + * @param channelNumber the channel number + * @param channelType the channel type + * @param networkNumber the network number + * @return true, if successful + * @since 1.0 + */ + public boolean ANTAssignChannel(byte channelNumber, byte channelType, byte networkNumber) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTAssignChannel(channelNumber, channelType, networkNumber); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set channel id. + * + * @param channelNumber the channel number + * @param deviceNumber the device number + * @param deviceType the device type + * @param txType the tx type + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetChannelId(byte channelNumber, short deviceNumber, byte deviceType, byte txType) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType, txType); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set channel period. + * + * @param channelNumber the channel number + * @param channelPeriod the channel period + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetChannelPeriod(byte channelNumber, short channelPeriod) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set channel rf freq. + * + * @param channelNumber the channel number + * @param radioFrequency the radio frequency + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetChannelRFFreq(byte channelNumber, byte radioFrequency) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetChannelRFFreq(channelNumber, radioFrequency); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set channel search timeout. + * + * @param channelNumber the channel number + * @param searchTimeout the search timeout + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetChannelSearchTimeout(byte channelNumber, byte searchTimeout) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetChannelSearchTimeout(channelNumber, searchTimeout); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set low priority channel search timeout. + * + * @param channelNumber the channel number + * @param searchTimeout the search timeout + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetLowPriorityChannelSearchTimeout(byte channelNumber, byte searchTimeout) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, searchTimeout); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set proximity search. + * + * @param channelNumber the channel number + * @param searchThreshold the search threshold + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetProximitySearch(byte channelNumber, byte searchThreshold) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetProximitySearch(channelNumber, searchThreshold); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT set channel transmit power + * @param channelNumber the channel number + * @param txPower the transmit power level + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSetChannelTxPower(byte channelNumber, byte txPower) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSetChannelTxPower(channelNumber, txPower); + } catch (RemoteException ex) + { + antServiceConnectionLost(); + } + return result; + } + + + /** + * ANT add channel id. + * + * @param channelNumber the channel number + * @param deviceNumber the device number + * @param deviceType the device type + * @param txType the tx type + * @param listIndex the list index + * @return true, if successful + * @since 1.0 + */ + public boolean ANTAddChannelId(byte channelNumber, short deviceNumber, byte deviceType, byte txType, byte listIndex) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTAddChannelId(channelNumber, deviceNumber, deviceType, txType, listIndex); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT config list. + * + * @param channelNumber the channel number + * @param listSize the list size + * @param exclude the exclude + * @return true, if successful + * @since 1.0 + */ + public boolean ANTConfigList(byte channelNumber, byte listSize, byte exclude) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTConfigList(channelNumber, listSize, exclude); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT config event buffering. + * + * @param screenOnFlushTimerInterval the screen on flush timer interval + * @param screenOnFlushBufferThreshold the screen on flush buffer threshold + * @param screenOffFlushTimerInterval the screen off flush timer interval + * @param screenOffFlushBufferThreshold the screen off flush buffer threshold + * @return true, if successful + * @since 1.3 + */ + public boolean ANTConfigEventBuffering(short screenOnFlushTimerInterval, short screenOnFlushBufferThreshold, short screenOffFlushTimerInterval, short screenOffFlushBufferThreshold) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTConfigEventBuffering((int)screenOnFlushTimerInterval, (int)screenOnFlushBufferThreshold, (int)screenOffFlushTimerInterval, (int)screenOffFlushBufferThreshold); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT disable event buffering. + * + * @return true, if successful + * @since 1.1 + */ + public boolean ANTDisableEventBuffering() + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTDisableEventBuffering(); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT open channel. + * + * @param channelNumber the channel number + * @return true, if successful + * @since 1.0 + */ + public boolean ANTOpenChannel(byte channelNumber) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTOpenChannel(channelNumber); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT close channel. + * + * @param channelNumber the channel number + * @return true, if successful + * @since 1.0 + */ + public boolean ANTCloseChannel(byte channelNumber) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTCloseChannel(channelNumber); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT request message. + * + * @param channelNumber the channel number + * @param messageID the message id + * @return true, if successful + * @since 1.0 + */ + public boolean ANTRequestMessage(byte channelNumber, byte messageID) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTRequestMessage(channelNumber, messageID); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT send broadcast data. + * + * @param channelNumber the channel number + * @param txBuffer the tx buffer + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSendBroadcastData(byte channelNumber, byte[] txBuffer) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSendBroadcastData(channelNumber, txBuffer); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT send acknowledged data. + * + * @param channelNumber the channel number + * @param txBuffer the tx buffer + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSendAcknowledgedData(byte channelNumber, byte[] txBuffer) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSendAcknowledgedData(channelNumber, txBuffer); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * ANT send burst transfer packet. + * + * @param control the control + * @param txBuffer the tx buffer + * @return true, if successful + * @since 1.0 + */ + public boolean ANTSendBurstTransferPacket(byte control, byte[] txBuffer) + { + if(!sServiceConnected) + { + return false; + } + + boolean result = false; + try { + result = sAntReceiver.ANTSendBurstTransferPacket(control, txBuffer); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + return result; + } + + /** + * Transmits the given data on channelNumber as part of a burst message. + * + * @param channelNumber Which channel to transmit on. + * @param txBuffer The data to send. + * @param initialPacket Which packet in the burst sequence does the data begin in, 1 is the first. + * @param containsEndOfBurst Is this the last of the data to be sent in burst. + * @return The number of bytes still to be sent (approximately). 0 if success. + */ + public int ANTSendBurstTransfer(byte channelNumber, byte[] txBuffer) + { + int result = txBuffer.length; + + if(sServiceConnected) + { + try { + result = sAntReceiver.ANTSendBurstTransfer(channelNumber, txBuffer); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + } + return result; + } + + /** + * ANT send partial burst. + * + * @param channelNumber the channel number + * @param txBuffer the tx buffer + * @param initialPacket the initial packet + * @param containsEndOfBurst the contains end of burst + * @return the int + * @since 1.0 + */ + public int ANTSendPartialBurst(byte channelNumber, byte[] txBuffer, int initialPacket, boolean containsEndOfBurst) + { + int result = txBuffer.length; + + if(sServiceConnected) + { + try { + result = sAntReceiver.ANTTransmitBurst(channelNumber, txBuffer, initialPacket, containsEndOfBurst); + } catch (RemoteException ex) { + antServiceConnectionLost(); + } + } + return result; + } + + /** + * Returns the version code (eg. 1) of ANTLib used by the ANT application service + * + * @return the service library version code + * @throws RemoteException the remote exception + * @since 1.2 + */ + public int getServiceLibraryVersionCode() throws RemoteException + { + if(mServiceLibraryVersionCode == 0) + { + mServiceLibraryVersionCode = sAntReceiver.getServiceLibraryVersionCode(); + } + + return mServiceLibraryVersionCode; + } + + /** + * Returns the version name (eg "1.0") of ANTLib used by the ANT application service + * + * @return the service library version name + * @throws RemoteException the remote exception + * @since 1.2 + */ + public String getServiceLibraryVersionName() throws RemoteException + { + return sAntReceiver.getServiceLibraryVersionName(); + } + + + // + // ------------------- The below functions are for debugging/development and will likely disappear. + // + + /** + * Turn verbose logging on or off in the service. + * + * @param debug Whether debug logging should be enabled. + * @return true, if successful + * @since 1.4 + */ + public boolean debugLogging(boolean debug) + { + if(DEBUG) Log.d(TAG, "debugLogging"); + + if(!sServiceSettingsConnected) + { + // Haven't received 'onConnected' notification yet + return false; + } + + boolean result = false; + try { + sServiceSettingsReceiver.debugLogging(debug); + result = true; + } catch (RemoteException ex) { + antServiceSettingsConnectionLost(); + } + return result; + } + + /** + * Set how many ANT packets should be combined in one request to the hardware during a burst transfer. + * + * @param numPackets Combined packet count. + * @return true, if successful + * @since 1.4 + */ + boolean setNumCombinedBurstPackets(int numPackets) + { + if(DEBUG) Log.d(TAG, "setNumCombinedBurstPackets"); + + if(!sServiceSettingsConnected) + { + // Haven't received 'onConnected' notification yet + return false; + } + + boolean result = false; + try { + sServiceSettingsReceiver.setNumCombinedBurstPackets(numPackets); + result = true; + } catch (RemoteException ex) { + antServiceSettingsConnectionLost(); + } + + return result; + } + + /** + * Get the maximum number of ANT packets which will be combined in one request to the hardware during a burst transfer. + * + * @return The number of combined packets, or -1 on failure. + * @since 1.4 + */ + int getNumCombinedBurstPackets() + { + if(DEBUG) Log.d(TAG, "getNumCombinedBurstPackets"); + + if(!sServiceSettingsConnected) + { + // Haven't received 'onConnected' notification yet + return -1; + } + + int result = -1; + try { + result = sServiceSettingsReceiver.getNumCombinedBurstPackets(); + } catch (RemoteException ex) { + antServiceSettingsConnectionLost(); + } + + return result; + } +} diff --git a/MyTracks/src/com/dsi/ant/AntInterfaceIntent.java b/MyTracks/src/com/dsi/ant/AntInterfaceIntent.java new file mode 100644 index 000000000..1b0b7f7e7 --- /dev/null +++ b/MyTracks/src/com/dsi/ant/AntInterfaceIntent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + +/** + * Manages the ANT Interface + * + * @hide + */ +public interface AntInterfaceIntent { + + public static final String STATUS = "com.dsi.ant.rx.intent.STATUS"; + public static final String ANT_MESSAGE = "com.dsi.ant.intent.ANT_MESSAGE"; + + public static final String ANT_ENABLED_ACTION = "com.dsi.ant.intent.action.ANT_ENABLED"; + public static final String ANT_DISABLED_ACTION = "com.dsi.ant.intent.action.ANT_DISABLED"; + public static final String ANT_RESET_ACTION = "com.dsi.ant.intent.action.ANT_RESET"; + + public static final String ANT_RX_MESSAGE_ACTION = "com.dsi.ant.intent.action.ANT_RX_MESSAGE_ACTION"; + +} diff --git a/MyTracks/src/com/dsi/ant/AntMesg.java b/MyTracks/src/com/dsi/ant/AntMesg.java new file mode 100644 index 000000000..cf2ae70d9 --- /dev/null +++ b/MyTracks/src/com/dsi/ant/AntMesg.java @@ -0,0 +1,279 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + +/** + * Public API for controlling the Ant Service. + * AntMesg contains definitions for ANT message IDs + * + * @hide + */ +public class AntMesg { + + ///////////////////////////////////////////////////////////////////////////// + // HCI VS Message Format + // Messages are in the format: + // + // Outgoing ANT messages (host -> ANT chip) + // 01 D1 FD XX YY YY II JJ ------ + // ^ ^ ^ ^ + // | HCI framing | | ANT Mesg | + // + // where: 01 is the 1 byte HCI packet Identifier (HCI Command packet) + // D1 FD is the 2 byte HCI op code (0xFDD1 stored in little endian) + // XX is the 1 byte Length of all parameters in bytes (number of bytes in the HCI packet after this byte) + // YY YY is the 2 byte Parameter describing the length of the entire ANT message (II JJ ------) stored in little endian + // II is the 1 byte size of the ANT message (0-249) + // JJ is the 1 byte ID of the ANT message (1-255, 0 is invalid) + // ------ is the data of the ANT message (0-249 bytes of data) + // + // Incoming HCI Command Complete for ANT message command (host <- ANT chip) + // 04 0E 04 01 D1 FD ZZ + // + // where: 04 is the 1 byte HCI packet Identifier (HCI Event packet) + // 0E is the 1 byte HCI event (Command Complete) + // 04 is the 1 byte Length of all parameters in bytes (there are 4 bytes) + // 01 is the 1 byte Number of parameters in the packet (there is 1 parameter) + // D1 FD is the 2 byte HCI Op code of the command (0xFDD1 stored in little endian) + // ZZ is the 1 byte response to the command (0x00 - Command Successful + // 0x1F - Returned if the receive message queue of the ANT chip is full, the command should be retried + // Other - Any other non-zero response code indicates an error) + // + // Incoming ANT messages (host <- ANT chip) + // 04 FF XX 00 05 YY YY II JJ ------ + // ^ ^ ^ ^ + // | HCI framing | | ANT Mesg | + // + // where: 04 is the 1 byte HCI packet Identifier (HCI Event packet) + // FF is the 1 byte HCI event code (0xFF Vendor Specific event) + // XX is the 1 byte Length of all parameters in bytes (number of bytes in the HCI packet after this byte) + // 00 05 is the 2 byte vendor specific event code for ANT messages (0x0500 stored in little endian) + // YY YY is the 2 byte Parameter describing the length of the entire ANT message (II JJ ------) stored in little endian + // II is the 1 byte size of the ANT message (0-249) + // JJ is the 1 byte ID of the ANT message (1-255, 0 is invalid) + // ------ is the data of the ANT message (0-249 bytes of data) + // + ///////////////////////////////////////////////////////////////////////////// + + public static final byte MESG_SYNC_SIZE =((byte)0); // Ant messages are embedded in HCI messages we do not include a sync byte + public static final byte MESG_SIZE_SIZE =((byte)1); + public static final byte MESG_ID_SIZE =((byte)1); + public static final byte MESG_CHANNEL_NUM_SIZE =((byte)1); + public static final byte MESG_EXT_MESG_BF_SIZE =((byte)1); // NOTE: this could increase in the future + public static final byte MESG_CHECKSUM_SIZE =((byte)0); // Ant messages are embedded in HCI messages we do not include a checksum + public static final byte MESG_DATA_SIZE =((byte)9); + + // The largest serial message is an ANT data message with all of the extended fields + public static final byte MESG_ANT_MAX_PAYLOAD_SIZE =AntDefine.ANT_STANDARD_DATA_PAYLOAD_SIZE; + + public static final byte MESG_MAX_EXT_DATA_SIZE =(AntDefine.ANT_EXT_MESG_DEVICE_ID_FIELD_SIZE + AntDefine.ANT_EXT_STRING_SIZE); // ANT device ID (4 bytes) + Padding for ANT EXT string size(19 bytes) + + public static final byte MESG_MAX_DATA_SIZE =(MESG_ANT_MAX_PAYLOAD_SIZE + MESG_EXT_MESG_BF_SIZE + MESG_MAX_EXT_DATA_SIZE); // ANT data payload (8 bytes) + extended bitfield (1 byte) + extended data (10 bytes) + public static final byte MESG_MAX_SIZE_VALUE =(MESG_MAX_DATA_SIZE + MESG_CHANNEL_NUM_SIZE); // this is the maximum value that the serial message size value is allowed to be + public static final byte MESG_BUFFER_SIZE =(MESG_SIZE_SIZE + MESG_ID_SIZE + MESG_CHANNEL_NUM_SIZE + MESG_MAX_DATA_SIZE + MESG_CHECKSUM_SIZE); + public static final byte MESG_FRAMED_SIZE =(MESG_ID_SIZE + MESG_CHANNEL_NUM_SIZE + MESG_MAX_DATA_SIZE); + public static final byte MESG_HEADER_SIZE =(MESG_SYNC_SIZE + MESG_SIZE_SIZE + MESG_ID_SIZE); + public static final byte MESG_FRAME_SIZE =(MESG_HEADER_SIZE + MESG_CHECKSUM_SIZE); + public static final byte MESG_MAX_SIZE =(MESG_MAX_DATA_SIZE + MESG_FRAME_SIZE); + + public static final byte MESG_SIZE_OFFSET =(MESG_SYNC_SIZE); + public static final byte MESG_ID_OFFSET =(MESG_SYNC_SIZE + MESG_SIZE_SIZE); + public static final byte MESG_DATA_OFFSET =(MESG_HEADER_SIZE); + public static final byte MESG_RECOMMENDED_BUFFER_SIZE =((byte) 64); // This is the recommended size for serial message buffers if there are no RAM restrictions on the system + + ////////////////////////////////////////////// + // Message ID's + ////////////////////////////////////////////// + public static final byte MESG_INVALID_ID =((byte)0x00); + public static final byte MESG_EVENT_ID =((byte)0x01); + + public static final byte MESG_VERSION_ID =((byte)0x3E); + public static final byte MESG_RESPONSE_EVENT_ID =((byte)0x40); + + public static final byte MESG_UNASSIGN_CHANNEL_ID =((byte)0x41); + public static final byte MESG_ASSIGN_CHANNEL_ID =((byte)0x42); + public static final byte MESG_CHANNEL_MESG_PERIOD_ID =((byte)0x43); + public static final byte MESG_CHANNEL_SEARCH_TIMEOUT_ID =((byte)0x44); + public static final byte MESG_CHANNEL_RADIO_FREQ_ID =((byte)0x45); + public static final byte MESG_NETWORK_KEY_ID =((byte)0x46); + public static final byte MESG_RADIO_TX_POWER_ID =((byte)0x47); + public static final byte MESG_RADIO_CW_MODE_ID =((byte)0x48); + + public static final byte MESG_SYSTEM_RESET_ID =((byte)0x4A); + public static final byte MESG_OPEN_CHANNEL_ID =((byte)0x4B); + public static final byte MESG_CLOSE_CHANNEL_ID =((byte)0x4C); + public static final byte MESG_REQUEST_ID =((byte)0x4D); + + public static final byte MESG_BROADCAST_DATA_ID =((byte)0x4E); + public static final byte MESG_ACKNOWLEDGED_DATA_ID =((byte)0x4F); + public static final byte MESG_BURST_DATA_ID =((byte)0x50); + + public static final byte MESG_CHANNEL_ID_ID =((byte)0x51); + public static final byte MESG_CHANNEL_STATUS_ID =((byte)0x52); + public static final byte MESG_RADIO_CW_INIT_ID =((byte)0x53); + public static final byte MESG_CAPABILITIES_ID =((byte)0x54); + + public static final byte MESG_STACKLIMIT_ID =((byte)0x55); + + public static final byte MESG_SCRIPT_DATA_ID =((byte)0x56); + public static final byte MESG_SCRIPT_CMD_ID =((byte)0x57); + + public static final byte MESG_ID_LIST_ADD_ID =((byte)0x59); + public static final byte MESG_ID_LIST_CONFIG_ID =((byte)0x5A); + public static final byte MESG_OPEN_RX_SCAN_ID =((byte)0x5B); + + public static final byte MESG_EXT_CHANNEL_RADIO_FREQ_ID =((byte)0x5C); // OBSOLETE: (for 905 radio) + public static final byte MESG_EXT_BROADCAST_DATA_ID =((byte)0x5D); + public static final byte MESG_EXT_ACKNOWLEDGED_DATA_ID =((byte)0x5E); + public static final byte MESG_EXT_BURST_DATA_ID =((byte)0x5F); + + public static final byte MESG_CHANNEL_RADIO_TX_POWER_ID =((byte)0x60); + public static final byte MESG_GET_SERIAL_NUM_ID =((byte)0x61); + public static final byte MESG_GET_TEMP_CAL_ID =((byte)0x62); + public static final byte MESG_SET_LP_SEARCH_TIMEOUT_ID =((byte)0x63); + public static final byte MESG_SET_TX_SEARCH_ON_NEXT_ID =((byte)0x64); + public static final byte MESG_SERIAL_NUM_SET_CHANNEL_ID_ID =((byte)0x65); + public static final byte MESG_RX_EXT_MESGS_ENABLE_ID =((byte)0x66); + public static final byte MESG_RADIO_CONFIG_ALWAYS_ID =((byte)0x67); + public static final byte MESG_ENABLE_LED_FLASH_ID =((byte)0x68); + + public static final byte MESG_XTAL_ENABLE_ID =((byte)0x6D); + + public static final byte MESG_STARTUP_MESG_ID =((byte)0x6F); + public static final byte MESG_AUTO_FREQ_CONFIG_ID =((byte)0x70); + public static final byte MESG_PROX_SEARCH_CONFIG_ID =((byte)0x71); + public static final byte MESG_EVENT_BUFFERING_CONFIG_ID =((byte)0x74); + + + public static final byte MESG_CUBE_CMD_ID =((byte)0x80); + + public static final byte MESG_GET_PIN_DIODE_CONTROL_ID =((byte)0x8D); + public static final byte MESG_PIN_DIODE_CONTROL_ID =((byte)0x8E); + public static final byte MESG_FIT1_SET_AGC_ID =((byte)0x8F); + + public static final byte MESG_FIT1_SET_EQUIP_STATE_ID =((byte)0x91); // *** CONFLICT: w/ Sensrcore, Fit1 will never have sensrcore enabled + + // Sensrcore Messages + public static final byte MESG_SET_CHANNEL_INPUT_MASK_ID =((byte)0x90); + public static final byte MESG_SET_CHANNEL_DATA_TYPE_ID =((byte)0x91); + public static final byte MESG_READ_PINS_FOR_SECT_ID =((byte)0x92); + public static final byte MESG_TIMER_SELECT_ID =((byte)0x93); + public static final byte MESG_ATOD_SETTINGS_ID =((byte)0x94); + public static final byte MESG_SET_SHARED_ADDRESS_ID =((byte)0x95); + public static final byte MESG_ATOD_EXTERNAL_ENABLE_ID =((byte)0x96); + public static final byte MESG_ATOD_PIN_SETUP_ID =((byte)0x97); + public static final byte MESG_SETUP_ALARM_ID =((byte)0x98); + public static final byte MESG_ALARM_VARIABLE_MODIFY_TEST_ID =((byte)0x99); + public static final byte MESG_PARTIAL_RESET_ID =((byte)0x9A); + public static final byte MESG_OVERWRITE_TEMP_CAL_ID =((byte)0x9B); + public static final byte MESG_SERIAL_PASSTHRU_SETTINGS_ID =((byte)0x9C); + + public static final byte MESG_BIST_ID =((byte)0xAA); + + public static final byte MESG_UNLOCK_INTERFACE_ID =((byte)0xAD); + public static final byte MESG_SERIAL_ERROR_ID =((byte)0xAE); + public static final byte MESG_SET_ID_STRING_ID =((byte)0xAF); + + public static final byte MESG_PORT_GET_IO_STATE_ID =((byte)0xB4); + public static final byte MESG_PORT_SET_IO_STATE_ID =((byte)0xB5); + + public static final byte MESG_SLEEP_ID =((byte)0xC5); + public static final byte MESG_GET_GRMN_ESN_ID =((byte)0xC6); + public static final byte MESG_SET_USB_INFO_ID =((byte)0xC7); + + public static final byte MESG_COMMAND_COMPLETE_RESPONSE_ID =((byte)0xC8); + + ////////////////////////////////////////////// + // Command complete results + ////////////////////////////////////////////// + public static final byte MESG_COMMAND_COMPLETE_SUCCESS =((byte)0x00); + public static final byte MESG_COMMAND_COMPLETE_RETRY =((byte)0x1F); + + ////////////////////////////////////////////// + // Message Sizes + ////////////////////////////////////////////// + public static final byte MESG_INVALID_SIZE =((byte)0); + + public static final byte MESG_VERSION_SIZE =((byte)13); + public static final byte MESG_RESPONSE_EVENT_SIZE =((byte)3); + public static final byte MESG_CHANNEL_STATUS_SIZE =((byte)2); + + public static final byte MESG_UNASSIGN_CHANNEL_SIZE =((byte)1); + public static final byte MESG_ASSIGN_CHANNEL_SIZE =((byte)3); + public static final byte MESG_CHANNEL_ID_SIZE =((byte)5); + public static final byte MESG_CHANNEL_MESG_PERIOD_SIZE =((byte)3); + public static final byte MESG_CHANNEL_SEARCH_TIMEOUT_SIZE =((byte)2); + public static final byte MESG_CHANNEL_RADIO_FREQ_SIZE =((byte)2); + public static final byte MESG_CHANNEL_RADIO_TX_POWER_SIZE =((byte)2); + public static final byte MESG_NETWORK_KEY_SIZE =((byte)9); + public static final byte MESG_RADIO_TX_POWER_SIZE =((byte)2); + public static final byte MESG_RADIO_CW_MODE_SIZE =((byte)3); + public static final byte MESG_RADIO_CW_INIT_SIZE =((byte)1); + + public static final byte MESG_SYSTEM_RESET_SIZE =((byte)1); + public static final byte MESG_OPEN_CHANNEL_SIZE =((byte)1); + public static final byte MESG_CLOSE_CHANNEL_SIZE =((byte)1); + public static final byte MESG_REQUEST_SIZE =((byte)2); + + public static final byte MESG_CAPABILITIES_SIZE =((byte)6); + public static final byte MESG_STACKLIMIT_SIZE =((byte)2); + + public static final byte MESG_SCRIPT_DATA_SIZE =((byte)10); + public static final byte MESG_SCRIPT_CMD_SIZE =((byte)3); + + public static final byte MESG_ID_LIST_ADD_SIZE =((byte)6); + public static final byte MESG_ID_LIST_CONFIG_SIZE =((byte)3); + public static final byte MESG_OPEN_RX_SCAN_SIZE =((byte)1); + public static final byte MESG_EXT_CHANNEL_RADIO_FREQ_SIZE =((byte)3); + + public static final byte MESG_RADIO_CONFIG_ALWAYS_SIZE =((byte)2); + public static final byte MESG_RX_EXT_MESGS_ENABLE_SIZE =((byte)2); + public static final byte MESG_SET_TX_SEARCH_ON_NEXT_SIZE =((byte)2); + public static final byte MESG_SET_LP_SEARCH_TIMEOUT_SIZE =((byte)2); + + public static final byte MESG_SERIAL_NUM_SET_CHANNEL_ID_SIZE =((byte)3); + public static final byte MESG_ENABLE_LED_FLASH_SIZE =((byte)2); + public static final byte MESG_GET_SERIAL_NUM_SIZE =((byte)4); + public static final byte MESG_GET_TEMP_CAL_SIZE =((byte)4); + + public static final byte MESG_XTAL_ENABLE_SIZE =((byte)1); + public static final byte MESG_STARTUP_MESG_SIZE =((byte)1); + public static final byte MESG_AUTO_FREQ_CONFIG_SIZE =((byte)4); + public static final byte MESG_PROX_SEARCH_CONFIG_SIZE =((byte)2); + + public static final byte MESG_GET_PIN_DIODE_CONTROL_SIZE =((byte)1); + public static final byte MESG_PIN_DIODE_CONTROL_ID_SIZE =((byte)2); + public static final byte MESG_FIT1_SET_EQUIP_STATE_SIZE =((byte)2); + public static final byte MESG_FIT1_SET_AGC_SIZE =((byte)3); + + public static final byte MESG_BIST_SIZE =((byte)6); + public static final byte MESG_UNLOCK_INTERFACE_SIZE =((byte)1); + public static final byte MESG_SET_SHARED_ADDRESS_SIZE =((byte)3); + + public static final byte MESG_GET_GRMN_ESN_SIZE =((byte)5); + + public static final byte MESG_PORT_SET_IO_STATE_SIZE =((byte)5); + + public static final byte MESG_EVENT_BUFFERING_CONFIG_SIZE =((byte)6); + + public static final byte MESG_SLEEP_SIZE =((byte)1); + + + public static final byte MESG_EXT_DATA_SIZE =((byte)13); + + protected AntMesg() + { } +} diff --git a/MyTracks/src/com/dsi/ant/IAnt.aidl b/MyTracks/src/com/dsi/ant/IAnt.aidl new file mode 100644 index 000000000..8ee999070 --- /dev/null +++ b/MyTracks/src/com/dsi/ant/IAnt.aidl @@ -0,0 +1,70 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + +/** + * The Android Ant API is not finalized, and *will* be updated and expanded. + * This is the base level ANT messaging API and gives any application full + * control over the ANT radio HW. Caution should be exercised when using + * this interface. + * + * Public API for controlling the Ant Service. + * + * {@hide} + */ +interface IAnt { +// Since version 1 (1.0): + boolean enable(); + boolean disable(); + boolean isEnabled(); + + boolean ANTTxMessage(in byte[] message); + + boolean ANTResetSystem(); + boolean ANTUnassignChannel(byte channelNumber); + boolean ANTAssignChannel(byte channelNumber, byte channelType, byte networkNumber); + boolean ANTSetChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType); + boolean ANTSetChannelPeriod(byte channelNumber, int channelPeriod); + boolean ANTSetChannelRFFreq(byte channelNumber, byte radioFrequency); + boolean ANTSetChannelSearchTimeout(byte channelNumber, byte searchTimeout); + boolean ANTSetLowPriorityChannelSearchTimeout(byte channelNumber, byte searchTimeout); + boolean ANTSetProximitySearch(byte channelNumber, byte searchThreshold); + boolean ANTSetChannelTxPower(byte channelNumber, byte txPower); + boolean ANTAddChannelId(byte channelNumber, int deviceNumber, byte deviceType, byte txType, byte listIndex); + boolean ANTConfigList(byte channelNumber, byte listSize, byte exclude); + + boolean ANTOpenChannel(byte channelNumber); + boolean ANTCloseChannel(byte channelNumber); + + boolean ANTRequestMessage(byte channelNumber, byte messageID); + boolean ANTSendBroadcastData(byte channelNumber, in byte[] txBuffer); + boolean ANTSendAcknowledgedData(byte channelNumber, in byte[] txBuffer); + + boolean ANTSendBurstTransferPacket(byte control, in byte[] txBuffer); + + int ANTSendBurstTransfer(byte channelNumber, in byte[] txBuffer); + int ANTTransmitBurst(byte channelNumber, in byte[] txBuffer, int initialPacket, boolean containsEndOfBurst); + +// Since version 4 (1.3): + boolean ANTConfigEventBuffering(int screenOnFlushTimerInterval, int screenOnFlushBufferThreshold, int screenOffFlushTimerInterval, int screenOffFlushBufferThreshold); + +// Since version 2 (1.1): + boolean ANTDisableEventBuffering(); + +// Since version 3 (1.2): + int getServiceLibraryVersionCode(); + String getServiceLibraryVersionName(); +} diff --git a/MyTracks/src/com/dsi/ant/IServiceSettings.aidl b/MyTracks/src/com/dsi/ant/IServiceSettings.aidl new file mode 100644 index 000000000..3d0cf61c8 --- /dev/null +++ b/MyTracks/src/com/dsi/ant/IServiceSettings.aidl @@ -0,0 +1,29 @@ +/* + * Copyright 2010 Dynastream Innovations 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.dsi.ant; + +/** + * + * API for configuring the non-ANT components of the ANT Service. + * + * {@hide} + */ +interface IServiceSettings { + void debugLogging(boolean debug); + + boolean setNumCombinedBurstPackets(int numPackets); + int getNumCombinedBurstPackets(); +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/AntPreference.java b/MyTracks/src/com/google/android/apps/mytracks/AntPreference.java new file mode 100644 index 000000000..6d6a4b853 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/AntPreference.java @@ -0,0 +1,62 @@ +/* + * 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 com.google.android.maps.mytracks.R; + +import android.content.Context; +import android.preference.Preference; +import android.util.AttributeSet; + +/** + * A preference for an ANT device pairing. + * Currently this shows the ID and lets the user clear that ID for future pairing. + * TODO: Support pairing from this preference. + * + * @author Sandor Dornbush + */ +public class AntPreference extends Preference { + + public AntPreference(Context context) { + super(context); + init(); + } + + public AntPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + int sensorId = getPersistedInt(0); + if (sensorId == 0) { + setSummary(R.string.settings_ant_not_paired); + } else { + setSummary(String.format(getContext().getString(R.string.settings_ant_paired), sensorId)); + } + + // Add actions to allow repairing. + setOnPreferenceClickListener( + new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + AntPreference.this.persistInt(0); + setSummary(R.string.settings_ant_not_paired); + return true; + } + }); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java index 3a276f86e..1eb86831a 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java @@ -46,6 +46,7 @@ public class ExportAllTracks { public static final int GPX_OPTION_INDEX = 0; public static final int KML_OPTION_INDEX = 1; public static final int CSV_OPTION_INDEX = 2; + public static final int TCX_OPTION_INDEX = 3; private final Activity activity; private WakeLock wakeLock; @@ -67,6 +68,11 @@ public class ExportAllTracks { case CSV_OPTION_INDEX: format = TrackFileFormat.CSV; break; + case TCX_OPTION_INDEX: + format = TrackFileFormat.TCX; + break; + default: + Log.w(MyTracksConstants.TAG, "Unknown export format: " + which); } } }; 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 55147ebd3..f44823bce 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/TrackRecordingService.java @@ -644,7 +644,6 @@ public class TrackRecordingService extends Service implements LocationListener { locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); splitManager = new SplitManager(this); - sensorManager = SensorManagerFactory.getSensorManager(this); prefManager = new PreferenceManager(this); registerLocationListener(); @@ -731,7 +730,7 @@ public class TrackRecordingService extends Service implements LocationListener { splitManager.shutdown(); splitManager = null; if (sensorManager != null) { - sensorManager.onDestroy(); + sensorManager.shutdown(); sensorManager = null; } @@ -1080,6 +1079,7 @@ public class TrackRecordingService extends Service implements LocationListener { showNotification(); registerLocationListener(); splitManager.restore(); + sensorManager = SensorManagerFactory.getSensorManager(this); if (sensorManager != null) { sensorManager.onStartTrack(); } @@ -1128,6 +1128,7 @@ public class TrackRecordingService extends Service implements LocationListener { if (sensorManager != null) { sensorManager.shutdown(); + sensorManager = null; } releaseWakeLock(); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java new file mode 100644 index 000000000..a26d0e8cd --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManager.java @@ -0,0 +1,169 @@ +/* + * 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 static com.google.android.apps.mytracks.MyTracksConstants.TAG; +import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.content.Sensor; +import com.google.android.maps.mytracks.R; + +import com.dsi.ant.AntDefine; +import com.dsi.ant.AntMesg; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +/** + * A sensor manager to connect ANT+ sensors. + * This can include heart rate monitors. + * + * @author Sandor Dornbush + */ +public class AntDirectSensorManager extends AntSensorManager { + + /* + * These constants are defined by the ANT+ heart rate monitor spec. + */ + public static final byte HRM_CHANNEL = 0; + public static final byte NETWORK_NUMBER = 1; + public static final byte HEART_RATE_DEVICE_TYPE = 120; + public static final byte POWER_DEVICE_TYPE = 11; + public static final byte MANUFACTURER_ID = 1; + public static final short CHANNEL_PERIOD = 8070; + public static final byte RF_FREQUENCY = 57; + + private short deviceNumberHRM; + + public AntDirectSensorManager(Context context) { + super(context); + + deviceNumberHRM = WILDCARD; + + // First read the the device id that we will be pairing with. + SharedPreferences prefs = context.getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, 0); + if (prefs != null) { + deviceNumberHRM = + (short) prefs.getInt(context.getString(R.string.ant_heart_rate_sensor_id_key), 0); + } + Log.i(TAG, "Pairing with heart rate monitor: " + deviceNumberHRM); + } + + @Override + public void handleMessage(byte[] antMessage) { + int channel = + antMessage[AntMesg.MESG_DATA_OFFSET] & AntDefine.CHANNEL_NUMBER_MASK; + switch (channel) { + case HRM_CHANNEL: + antDecodeHRM(antMessage); + break; + default: + Log.d(TAG, "Unhandled message: " + channel); + } + } + + /** + * Decode an ant heart rate monitor message. + * @param antMessage The byte array received from the heart rate monitor. + */ + private void antDecodeHRM(byte[] antMessage) { + // message ID == broadcast + switch (antMessage[AntMesg.MESG_ID_OFFSET]) { + case AntMesg.MESG_BROADCAST_DATA_ID: + handleBroadcastData(antMessage); + break; + case AntMesg.MESG_RESPONSE_EVENT_ID: + handleMessageResponse(antMessage); + break; + case AntMesg.MESG_CHANNEL_ID_ID: + handleChannelId(antMessage); + break; + default: + Log.e(TAG, "Unexpected message id: " + antMessage[3]); + } + } + + private void handleBroadcastData(byte[] antMessage) { + if (deviceNumberHRM == WILDCARD) { + getAntReceiver().ANTRequestMessage(HRM_CHANNEL, + AntMesg.MESG_CHANNEL_ID_ID); + Log.d(TAG, "Requesting channel id id."); + } + + setSensorState(Sensor.SensorState.CONNECTED); + int bpm = (int) antMessage[10] & 0xFF; + Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() + .setValue(bpm) + .setState(Sensor.SensorState.SENDING); + sensorData = + Sensor.SensorDataSet.newBuilder() + .setCreationTime(System.currentTimeMillis()) + .setHeartRate(b) + .build(); + } + + void handleChannelId(byte[] antMessage) { + // Store the device id. + deviceNumberHRM = + (short) (((int) antMessage[3] & 0xFF | + ((int) (antMessage[4] & 0xFF) << 8)) & 0xFFFF); + Log.i(TAG, "Found device id: " + deviceNumberHRM); + + SharedPreferences prefs = context.getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(context.getString(R.string.ant_heart_rate_sensor_id_key), deviceNumberHRM); + editor.commit(); + } + + private void handleMessageResponse(byte[] antMessage) { + if (antMessage[3] == AntMesg.MESG_EVENT_ID && + antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) { + // Search timeout + Log.w(TAG, "Search timed out. Unassigning channel."); + getAntReceiver().ANTUnassignChannel((byte) 0); + setSensorState(Sensor.SensorState.DISCONNECTED); + } else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) { + setSensorState(Sensor.SensorState.DISCONNECTED); + Log.i(TAG, "Disconnected from the sensor: " + getSensorState()); + } + } + + /** + * Open a channel to the SRM head unit. + */ + @Override + public void setupChannel() { + super.setupChannel(); + antChannelSetup(NETWORK_NUMBER, + HRM_CHANNEL, + deviceNumberHRM, + HEART_RATE_DEVICE_TYPE, + (byte) 0x01, + CHANNEL_PERIOD, + RF_FREQUENCY, + (byte) 0); + setSensorState(Sensor.SensorState.CONNECTING); + } + + public short getDeviceNumberHRM() { + return deviceNumberHRM; + } + + void setDeviceNumberHRM(short deviceNumberHRM) { + this.deviceNumberHRM = deviceNumberHRM; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java new file mode 100644 index 000000000..3d038a922 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSRMSensorManager.java @@ -0,0 +1,226 @@ +/* + * 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.apps.mytracks.content.Sensor; +import com.google.android.maps.mytracks.R; + +import com.dsi.ant.AntDefine; +import com.dsi.ant.AntMesg; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +/** + * A sensor manager that can connect to an SRM Power Control 7 head unit. + * + * @author Sandor Dornbush + */ +public class AntSRMSensorManager extends AntSensorManager { + + /* + * These constants are defined by the SRM Headunit spec. + */ + public static final byte NETWORK_NUMBER = (byte) 0; + public static final byte DEVICE_TYPE = (byte) 12; + public static final byte MANUFACTURER_ID = (byte) 1; + public static final short CHANNEL_PERIOD = (short) 8192; + public static final byte RF_FREQUENCY = (byte) 50; + + public static final int MESSAGE_TYPE_INDEX = 3; + public static final int INITIALS_MESSAGE_TYPE = 5; + public static final int SENSOR_DATA_MESSAGE_TYPE = 6; + + public static final int MESSAGE_ID_INDEX = 4; + + /** + * This can be any value between 0..8 + */ + private byte channel = (byte) 5; + + /** + * This is the id of this device. + * The head unit must be programmed to expect this number. + */ + private byte deviceId; + + /** + * The id of the last message received from the SRM head unit. + */ + private int lastMessageId; + + public AntSRMSensorManager(Context context) { + super(context); + + // First read the the device id that we will be announcing. + SharedPreferences prefs = context.getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, 0); + if (prefs != null) { + deviceId = (byte) prefs.getInt( + context.getString(R.string.ant_srm_bridge_sensor_id_key), + WILDCARD); + } + } + + @Override + public void handleMessage(byte[] antMessage) { + // Parse channel number + byte recievedChannel = (byte) (antMessage[AntMesg.MESG_DATA_OFFSET] + & AntDefine.CHANNEL_NUMBER_MASK); + if (recievedChannel != channel) { + Log.d(MyTracksConstants.TAG, "Unexpected channel: " + recievedChannel); + return; + } + switch (antMessage[AntMesg.MESG_ID_OFFSET]) { + case AntMesg.MESG_BROADCAST_DATA_ID: + handleBroadcastData(antMessage); + break; + case AntMesg.MESG_RESPONSE_EVENT_ID: + handleMessageResponse(antMessage); + break; + case AntMesg.MESG_CHANNEL_ID_ID: + handleChannelId(antMessage); + break; + default: + Log.e(MyTracksConstants.TAG, + "Unexpected message id: " + antMessage[3]); + } + } + + private void handleBroadcastData(byte[] antMessage) { + if (deviceId == WILDCARD) { + getAntReceiver().ANTRequestMessage(channel, AntMesg.MESG_CHANNEL_ID_ID); + Log.d(MyTracksConstants.TAG, "Requesting channel id id."); + } + setSensorState(Sensor.SensorState.CONNECTED); + switch(antMessage[MESSAGE_TYPE_INDEX]) { + case INITIALS_MESSAGE_TYPE: + // TODO handle initials message. + break; + case SENSOR_DATA_MESSAGE_TYPE: + parseSensorData(antMessage); + break; + default: + Log.e(MyTracksConstants.TAG, + "Unexpected message type: " + antMessage[MESSAGE_TYPE_INDEX]); + } + } + + private void handleChannelId(byte[] antMessage) { + // Store the device id. + deviceId = antMessage[3]; + Log.i(MyTracksConstants.TAG, "Found device id: " + deviceId); + + SharedPreferences prefs = context.getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(context.getString(R.string.ant_srm_bridge_sensor_id_key), deviceId); + editor.commit(); + } + + private void handleMessageResponse(byte[] antMessage) { + if (antMessage[3] == AntMesg.MESG_EVENT_ID + && antMessage[4] == AntDefine.EVENT_RX_SEARCH_TIMEOUT) { + // Search timeout + Log.w(MyTracksConstants.TAG, "Search timed out. Unassigning channel."); + getAntReceiver().ANTUnassignChannel(channel); + setSensorState(Sensor.SensorState.DISCONNECTED); + } else if (antMessage[3] == AntMesg.MESG_UNASSIGN_CHANNEL_ID) { + setSensorState(Sensor.SensorState.DISCONNECTED); + Log.i(MyTracksConstants.TAG, + "Disconnected from the sensor: " + getSensorState()); + } + } + + private void parseSensorData(byte[] antMessage) { + if (antMessage.length != 11) { + Log.e(MyTracksConstants.TAG, + "Unexpected ant message length: " + antMessage.length); + return; + } + + int newMessageId = antMessage[MESSAGE_ID_INDEX] & 0xFF; + if (lastMessageId == newMessageId) { + // Repeated message. + Log.i(MyTracksConstants.TAG, + String.format("SRM ignoring repeat: 0x%X", newMessageId)); + return; + } + if (newMessageId < lastMessageId) { + if (!(newMessageId < 20 && lastMessageId > 200)) { + Log.i(MyTracksConstants.TAG, + String.format("SRM ignoring repeat: 0x%X", newMessageId)); + return; + } // else assume the byte overflowed to 0. + } + lastMessageId = newMessageId; + + Sensor.SensorDataSet.Builder builder = Sensor.SensorDataSet.newBuilder() + .setCreationTime(System.currentTimeMillis()); + + int power = SensorUtils.unsignedShortToInt(antMessage, 5); + if (power >= 0) { + Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() + .setValue(power) + .setState(Sensor.SensorState.SENDING); + builder.setPower(b); + } + + int speed = SensorUtils.unsignedShortToInt(antMessage, 7); + if (speed >= 0) { + // The speed from srm is in 1/10 km/hr. + // Sensor data expects the speed as m/s. + // TODO finish this. + //sensorData.setWheelSpeed(UnitConversions.TENTH_KMH_TO_MPS * speed); + } + + int cadence = antMessage[9] & 0xFF; + if (cadence >= 0) { + Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() + .setValue(cadence) + .setState(Sensor.SensorState.SENDING); + builder.setCadence(b); + } + + int bpm = antMessage[10] & 0xFF; + if (bpm > 0) { + Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() + .setValue(bpm) + .setState(Sensor.SensorState.SENDING); + builder.setHeartRate(b); + } + sensorData = builder.build(); + } + + /** + * Open a channel to the SRM head unit. + */ + @Override + public void setupChannel() { + antChannelSetup(NETWORK_NUMBER, + channel, + deviceId, + DEVICE_TYPE, + MANUFACTURER_ID, + CHANNEL_PERIOD, + RF_FREQUENCY, + (byte) 0); + lastMessageId = 0; + setSensorState(Sensor.SensorState.CONNECTING); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSensorManager.java b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSensorManager.java new file mode 100644 index 000000000..eb2230f10 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/AntSensorManager.java @@ -0,0 +1,228 @@ +/* + * 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.content.Sensor.SensorDataSet; + +import com.dsi.ant.AntDefine; +import com.dsi.ant.AntInterface; +import com.dsi.ant.AntInterfaceIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +/** + * An abstract class for ANT+ based sensors. + * + * @author Sandor Dornbush + */ +public abstract class AntSensorManager extends SensorManager { + + // ANT Service + public static final int EVENT_ANT_ENABLED = 1; + public static final int EVENT_ANT_DISABLED = 2; + + // Pairing + protected static final short WILDCARD = 0; + + private AntInterface antReceiver; + + private boolean status; + + // Flag to know if the ANT App was interrupted + // TODO: This code path is not used but probably should be. + private boolean antInterrupted; + + /** + * The data from the sensors. + */ + protected SensorDataSet sensorData; + + protected Context context; + + private static final boolean DEBUGGING = false; + + /** + * Receives all of the ANT intents and dispatches to the proper handler. + */ + private final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String antAction = intent.getAction(); + + Log.i(MyTracksConstants.TAG, "enter onReceive" + antAction); + if (antAction.equals(AntInterfaceIntent.ANT_ENABLED_ACTION)) { + Log.i(MyTracksConstants.TAG, "enter onReceive ANT_ENABLED_ACTION " + + antAction); + handler.sendMessage(handler.obtainMessage(EVENT_ANT_ENABLED, 0)); + } + if (antAction.equals(AntInterfaceIntent.ANT_DISABLED_ACTION)) { + Log.i(MyTracksConstants.TAG, "enter onReceive ANT_DISABLED_ACTION " + + antAction); + handler.sendMessage(handler.obtainMessage(EVENT_ANT_DISABLED, 0)); + } + + if (antAction.equals(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION)) { + byte[] antMessage = + intent.getByteArrayExtra(AntInterfaceIntent.ANT_MESSAGE); + if (DEBUGGING) { + StringBuilder sb = + new StringBuilder("ANT_RX_MESSAGE_ACTION Rx:"); + + for (int i = 0; i < antMessage.length; i++) + sb.append(String.format("[%X]]", (antMessage[i] & 0xFF))); + + Log.d(MyTracksConstants.TAG, sb.toString()); + } + + handleMessage(antMessage); + } + } + }; + + /** + * Handler for all the ANT related events. The events will be handled only + * when we are in the ANT application main menu + **/ + private Handler handler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_ANT_ENABLED: + Log.i(MyTracksConstants.TAG, + "enter handleMessage ----EVENT_ANT_ENABLED"); + break; + case EVENT_ANT_DISABLED: + Log.i(MyTracksConstants.TAG, + "enter handleMessage ----EVENT_ANT_DISABLED"); + break; + } + } + }; + + public AntSensorManager(Context context) { + this.context = context; + + // Need to enable the ANT if it is not enabled earlier + antReceiver = AntInterface.getInstance(context, null); + if (antReceiver == null) { + return; + } + + status = antReceiver.isEnabled(); + if (!status) { + // Make sure not to call AntInterface.enable() again, if it has been + // already called before + if (antInterrupted == false) { + status = antReceiver.enable(); + if (!status) { + Log.e(MyTracksConstants.TAG, "Can not enable ANT interface"); + } else { + Log.i(MyTracksConstants.TAG, "Powering on Radio"); + } + } else { + Log.i(MyTracksConstants.TAG, "Radio already enabled"); + } + } + } + + @Override + public void setupChannel() { + Log.i(MyTracksConstants.TAG, "Registering for ant intents."); + // Register for ANT intent broadcasts. + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(AntInterfaceIntent.ANT_ENABLED_ACTION); + intentFilter.addAction(AntInterfaceIntent.ANT_DISABLED_ACTION); + intentFilter.addAction(AntInterfaceIntent.ANT_RX_MESSAGE_ACTION); + context.registerReceiver(receiver, intentFilter); + } + + @Override + public SensorDataSet getSensorDataSet() { + return sensorData; + } + + @Override + public boolean isEnabled() { + return true; + } + + public abstract void handleMessage(byte[] antMessage); + + @Override + public void onDestroy() { + Log.i(MyTracksConstants.TAG, "Unregistering for ant intents."); + context.unregisterReceiver(receiver); + } + + /** + * Set up an ANT+ channel. + * + * @return True on success. + */ + protected boolean antChannelSetup(byte networkNumber, byte channelNumber, + short deviceNumber, byte deviceType, byte txType, short channelPeriod, + byte radioFreq, byte proxSearch) { + + // Assign as slave channel on selected network (0 = public, 1 = ANT+, 2 = + // ANTFS) + if (!antReceiver.ANTAssignChannel(channelNumber, + AntDefine.PARAMETER_RX_NOT_TX, networkNumber)) { + return false; + } + + if (!antReceiver.ANTSetChannelId(channelNumber, deviceNumber, deviceType, + txType)) { + return false; + } + + if (!antReceiver.ANTSetChannelPeriod(channelNumber, channelPeriod)) { + return false; + } + + if (!antReceiver.ANTSetChannelRFFreq(channelNumber, radioFreq)) { + return false; + } + + // Disable high priority search + if (!antReceiver.ANTSetChannelSearchTimeout(channelNumber, (byte) 0)) { + return false; + } + + // Set search timeout to 30 seconds (low priority search)) + if (!antReceiver.ANTSetLowPriorityChannelSearchTimeout(channelNumber, (byte) 12)) { + return false; + } + + if (deviceNumber == WILDCARD) { + // Configure proximity search, if using wild card search + if (!antReceiver.ANTSetProximitySearch(channelNumber, proxSearch)) { + return false; + } + } + + return !antReceiver.ANTOpenChannel(channelNumber); + } + + public AntInterface getAntReceiver() { + return antReceiver; + } +} 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 01a8d8bf5..cad3e707b 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 @@ -13,7 +13,6 @@ * 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; @@ -46,15 +45,19 @@ public class SensorManagerFactory { return null; } - String sensor = prefs.getString(context.getString(R.string.sensor_type_key), - 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.ant_sensor_type))) { + return new AntDirectSensorManager(context); + } else if (sensor.equals(context.getString(R.string.srm_ant_bridge_sensor_type))) { + return new AntSRMSensorManager(context); } else if (sensor.equals(context.getString(R.string.zephyr_sensor_type))) { return new ZephyrSensorManager(context); } else { + Log.w(MyTracksConstants.TAG, "Unable to find sensor type: " + sensor); return null; } } 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 index 8cf8f951f..3a2fbf9e4 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java +++ b/MyTracks/src/com/google/android/apps/mytracks/services/sensors/ZephyrMessageParser.java @@ -35,8 +35,9 @@ public class ZephyrMessageParser implements MessageParser { } 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 + // TODO: Fix the cadence calculation. + // int cadence = SensorUtils.unsignedShortToInt(buffer, 56); + // Heart Rate Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder() .setValue(buffer[12] & 0xFF) .setState(Sensor.SensorState.SENDING); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java index 12542199b..73721214e 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/TrackWriterTest.java @@ -2,31 +2,27 @@ package com.google.android.apps.mytracks.io; -import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.isA; -import static org.easymock.EasyMock.leq; -import static org.easymock.EasyMock.same; -import com.google.android.apps.mytracks.MyTracksConstants; +import com.google.android.apps.mytracks.content.MyTracksProvider; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.Waypoint; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory; +import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext; import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory; import android.content.Context; -import android.database.MatrixCursor; import android.location.Location; -import android.provider.BaseColumns; import android.test.AndroidTestCase; +import android.test.RenamingDelegatingContext; +import android.test.mock.MockContentResolver; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.OutputStream; import org.easymock.EasyMock; -import org.easymock.IAnswer; import org.easymock.IArgumentMatcher; import org.easymock.IMocksControl; @@ -138,12 +134,19 @@ public class TrackWriterTest extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); + MockContentResolver mockContentResolver = new MockContentResolver(); + RenamingDelegatingContext targetContext = new RenamingDelegatingContext( + getContext(), getContext(), "test."); + Context context = new MockContext(mockContentResolver, targetContext); + MyTracksProvider provider = new MyTracksProvider(); + provider.attachInfo(context, null); + mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider); + setContext(context); + providerUtils = MyTracksProviderUtils.Factory.get(context); + oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils); + mocksControl = EasyMock.createStrictControl(); formatWriter = mocksControl.createMock(TrackFormatWriter.class); - providerUtils = mocksControl.createMock(MyTracksProviderUtils.class); - oldProviderUtilsFactory = - TestingProviderUtilsFactory.installWithInstance(providerUtils); - expect(formatWriter.getExtension()).andStubReturn(EXTENSION); track = new Track(); @@ -225,13 +228,6 @@ public class TrackWriterTest extends AndroidTestCase { public void testWriteDocument_emptyTrack() { writer = new TrackWriter(getContext(), providerUtils, track, formatWriter); - // Don't let it write any waypoints - expect(providerUtils.getWaypointsCursor( - TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)) - .andStubReturn(null); - expect(providerUtils.getLocationsCursor( - eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(null); - // Set expected mock behavior formatWriter.writeHeader(); formatWriter.writeFooter(); @@ -253,7 +249,7 @@ public class TrackWriterTest extends AndroidTestCase { new Location("fake2"), new Location("fake3"), new Location("fake4"), - new Location("fake5"), + new Location("fake5") }; Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() }; @@ -262,37 +258,14 @@ public class TrackWriterTest extends AndroidTestCase { // Make location 3 invalid locs[2].setLatitude(100); - - // Set up cursors - // We use fake columns since the cursor is only read by the provider utils - final MatrixCursor locCursor = - new MatrixCursor(new String[] { BaseColumns._ID }, 6); - for (int i = 1; i <= 6; i++) { - locCursor.newRow().add(i); + + assertEquals(locs.length, providerUtils.bulkInsertTrackPoints(locs, locs.length, TRACK_ID)); + for (int i = 0; i < wps.length; ++i) { + Waypoint wpt = wps[i]; + wpt.setTrackId(TRACK_ID); + assertNotNull(providerUtils.insertWaypoint(wpt)); + wpt.setId(i + 1); } - expect(providerUtils.getLocationsCursor( - eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(locCursor); - providerUtils.fillLocation(same(locCursor), isA(Location.class)); - EasyMock.expectLastCall().andStubAnswer(new IAnswer() { - @Override - public Void answer() throws Throwable { - Location loc = (Location) EasyMock.getCurrentArguments()[1]; - loc.set(locs[locCursor.getPosition()]); - return null; - } - }); - - MatrixCursor wpCursor = - new MatrixCursor(new String[] { BaseColumns._ID }, 3); - wpCursor.newRow().add(1); - wpCursor.newRow().add(2); - wpCursor.newRow().add(3); - expect(providerUtils.getWaypointsCursor( - eq(TRACK_ID), leq(0L), - eq(MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))) - .andStubReturn(wpCursor); - expect(providerUtils.createWaypoint(wpCursor)) - .andStubAnswer(stubCursorToArray(wpCursor, wps)); // Begin the track formatWriter.writeHeader(); @@ -317,8 +290,8 @@ public class TrackWriterTest extends AndroidTestCase { formatWriter.writeEndTrack(locEq(locs[5])); // Expect reading/writing of the waypoints (except the first) - formatWriter.writeWaypoint(wps[1]); - formatWriter.writeWaypoint(wps[2]); + formatWriter.writeWaypoint(wptEq(wps[1])); + formatWriter.writeWaypoint(wptEq(wps[2])); formatWriter.writeFooter(); formatWriter.close(); @@ -330,6 +303,26 @@ public class TrackWriterTest extends AndroidTestCase { mocksControl.verify(); } + private static Waypoint wptEq(final Waypoint wpt) { + EasyMock.reportMatcher(new IArgumentMatcher() { + @Override + public boolean matches(Object wptObj2) { + if (wptObj2 == null || wpt == null) return wpt == wptObj2; + Waypoint wpt2 = (Waypoint) wptObj2; + + return wpt.getId() == wpt2.getId(); + } + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("wptEq("); + buffer.append(wpt); + buffer.append(")"); + } + }); + return null; + } + private static Location locEq(final Location loc) { EasyMock.reportMatcher(new IArgumentMatcher() { @Override @@ -359,23 +352,14 @@ public class TrackWriterTest extends AndroidTestCase { }); return null; } - - private IAnswer stubCursorToArray( - final MatrixCursor cursor, final T[] values) { - return new IAnswer() { - @Override - public T answer() throws Throwable { - return values[cursor.getPosition()]; - } - }; - } - + private void fillLocations(Location... locs) { assertTrue(locs.length < 90); for (int i = 0; i < locs.length; i++) { Location location = locs[i]; location.setLatitude(i + 1); location.setLongitude(i + 1); + location.setTime(i + 1000); } } } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java new file mode 100644 index 000000000..22c2f0fa1 --- /dev/null +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/AntDirectSensorManagerTest.java @@ -0,0 +1,97 @@ +/* + * 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.dsi.ant.AntMesg; +import com.google.android.apps.mytracks.MyTracksSettings; +import com.google.android.apps.mytracks.content.Sensor; +import com.google.android.maps.mytracks.R; + +import android.content.SharedPreferences; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class AntDirectSensorManagerTest extends AndroidTestCase { + + private SharedPreferences sharedPreferences; + private AntDirectSensorManager manager; + + public void setUp() { + sharedPreferences = getContext().getSharedPreferences( + MyTracksSettings.SETTINGS_NAME, 0); + // Let's use default values. + sharedPreferences.edit().clear().commit(); + manager = new AntDirectSensorManager(getContext()); + } + + @SmallTest + public void testBroadcastData() { + manager.setDeviceNumberHRM((short) 42); + byte[] buff = new byte[11]; + buff[1] = AntMesg.MESG_BROADCAST_DATA_ID; + buff[2] = 0; // HRM CHANNEL + buff[10] = (byte) 220; + manager.handleMessage(buff); + + Sensor.SensorDataSet sds = manager.getSensorDataSet(); + assertNotNull(sds); + assertTrue(sds.hasHeartRate()); + assertEquals(Sensor.SensorState.SENDING, + sds.getHeartRate().getState()); + assertEquals(220, sds.getHeartRate().getValue()); + + assertFalse(sds.hasCadence()); + assertFalse(sds.hasPower()); + } + + @SmallTest + public void testHandleChannelId() { + assertEquals(0, manager.getDeviceNumberHRM()); + short id = 42; + byte[] buff = new byte[5]; + buff[3] = 42; + manager.handleChannelId(buff); + assertEquals(id, manager.getDeviceNumberHRM()); + } + + @SmallTest + public void testChannelId() { + byte[] buff = new byte[11]; + buff[1] = AntMesg.MESG_CHANNEL_ID_ID; + buff[3] = 42; + manager.handleMessage(buff); + + assertEquals(42, manager.getDeviceNumberHRM()); + assertEquals(42, + sharedPreferences.getInt( + getContext().getString(R.string.ant_heart_rate_sensor_id_key), -1)); + assertNull(manager.getSensorDataSet()); + } + + @SmallTest + public void testResponseEvent() { + assertEquals(Sensor.SensorState.NONE, manager.getSensorState()); + byte[] buff = new byte[11]; + buff[1] = AntMesg.MESG_RESPONSE_EVENT_ID; + buff[3] = AntMesg.MESG_UNASSIGN_CHANNEL_ID; + manager.handleMessage(buff); + + assertEquals(Sensor.SensorState.DISCONNECTED, manager.getSensorState()); + assertNull(manager.getSensorDataSet()); + } + + // TODO: Test timeout too. +} diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java index 4dcc0b9d9..3951aa3c1 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/services/sensors/SensorManagerFactoryTest.java @@ -28,12 +28,26 @@ public class SensorManagerFactoryTest extends AndroidTestCase { @SmallTest public void testCreateZephyr() throws Exception { + assertClassForName(ZephyrSensorManager.class, R.string.zephyr_sensor_type); + } + + @SmallTest + public void testCreateAnt() throws Exception { + assertClassForName(AntDirectSensorManager.class, R.string.ant_sensor_type); + } + + @SmallTest + public void testCreateAntSRM() throws Exception { + assertClassForName(AntSRMSensorManager.class, R.string.srm_ant_bridge_sensor_type); + } + + private void assertClassForName(Class c, int i) { sharedPreferences.edit() - .putString(getContext().getString(R.string.sensor_type_key), - getContext().getString(R.string.zephyr_sensor_type)) - .commit(); + .putString(getContext().getString(R.string.sensor_type_key), + getContext().getString(i)) + .commit(); SensorManager sm = SensorManagerFactory.getSensorManager(getContext()); assertNotNull(sm); - assertTrue(sm instanceof ZephyrSensorManager); + assertTrue(c.isInstance(sm)); } }