Buddy-Tracker (Technik)

Target API ist im Moment die 25 (Nougat 7.1) und die Minimum API ist 9. Anfänglich habe ich bis Level 8 unterstützt (da ich noch so ein uraltes Testgerät habe), aber die Gerätebasis ist jetzt zu klein geworden und die nötigen Klimmzüge für API-Levels unter 9 werden zu groß. Unterhalb von API 9 wird auch die Unterstützung seitens Google recht spärlich.

Nach einer umfangreichen Überarbeitung des Codes habe ich den Buddy-Tracker nun mit einer 1.x-Version geadelt. ;-) Hier hat fast der komplette Code eine Frischzellenkur bekommen. Dabei habe ich evaluiert wie sich die Location-Services der Google Play Services verhalten. Da die internen Aufrufe dieser Library hochgradig asynchron sind ist das auch ein gutes Feld um Erfahrungen mit reaktiver Programmierung zu sammeln. Ich habe also mit RxJava einen reaktiven Wrapper um diese Library geschrieben. Funktioniert so weit ganz gut, aber leider mußte ich das Ganze wieder deaktivieren, denn die Qualität der gelieferten Positionsdaten ist grottenschlecht. Man kann auch nicht nur GPS als Quelle der Positionsdaten auswählen, sondern muß immer auch den Netzwerk-Provider mitnutzen. Einige sehen das ja kritisch, weil dabei auch immer die eigenen Bewegungsdaten an Google gesendet werden. Aber dessenungeachtet ist die KI, die Google da verwendet offenbar noch verbesserungsfähig, denn auch wenn man Positionen mit hoher Genauigkeit anfordert, liefern die Location Services teilweise Positionen mit einer Genauigkeit von 1000m zurück - und das auch weit ab vom jeweiligen Track. Da ich das Ganze - Dependency-Injection sei Dank - konfigurierbar machen konnte, konnte ich jeweils den Gegencheck machen und dieselbe Strecke statt mit den Google Services mit den Android-Location Services abgehen. Hier sind die Positionsdaten erheblich besser und die Genauigkeit war (auf derselben Strecke!) immer besser als 20m - was man bei GPS unter freiem Himmel ja auch erwarten kann.

Inzwischen hatte es Google gefallen, die Aufrufbedingungen für die SyncAdapter zu ändern. Damit hatte ich die Synchronisation der eMails realisiert. Nun war die minimale Synchronisationsperiode von 10 Minuten auf eine Stunde hoch gesetzt worden. Viel zu hoch für meine Bedürfnisse. Also mußte eine andere Lösung gefunden werden. Ich hab dann den Firebase-JobScheduler eingesetzt der zwar noch Beta ist, aber den GCM-Networkmanager ablösen soll. Da im Moment alles funktioniert …

Beim Refactoring hat es sich angeboten, die bestehenden Unit- und Instrumentation-Tests zu erweitern. Gibt einem einfach viel mehr Sicherheit wenn man am Code rum schraubt. Der Buddy-Tracker ist inzwischen so komplex geworden, daß man nicht mehr immer alles gleichzeitig im Blick haben kann. Ist schon spannend, beobachten zu können wie sich der eigene Blickwinkel auf den Code über die Zeit langsam verändert hat.

Mir gefiel die ganze Zeit eigentlich noch nicht, wie die Daten geteilt werden konnten. Wegen den flexiblen Übertragungsmöglichkeiten konnte ich keinen Standard-Share Prozess nehmen, sondern mußte etwas eigenes bauen. Das hatte aber zur Folge, daß die ganzen Kontakte immer erst einmal im Buddy-Tracker eingetragen werden mussten. Schnell mal was an einen bestehenden Kontakt senden war so nicht möglich. ich habe dann den ShareActionProvider eingesetzt um die Information auch über andere bereits auf dem Smartphone installierte Apps teilen zu können. Aber die Art und Weise wie man den Share-Intent kontinuierlich aktuell halten mußte ohne daß sichergestellt war, daß die Share-Option überhaupt genutzt wurde, schmeckte mir nicht. Die Lösung ist DirectShare. Hier kann der Benutzer direkt mit häufig genutzten Kontakten teilen oder andere externe Apps nutzen. Eine dieser Apps ist dabei dann auch wieder der Buddy-Tracker, der dann den alten Dialog zur Gruppenauswahl der Buddies bereit stellt. Man hat jetzt also das Beste aus beiden Welten. Auf Geräten die noch nicht Lollipop haben funktioniert das auch, nur fällt die Option weg, direkt an Kontakte zu senden. Hier muß man die Auswahl dann jeweils in der gewählten App treffen.

Und es gibt noch etwas Eye-Candy: Für Buddies und Orte können Bilder angegeben werden, die dann bei den Markern auf der Karte verwendet werden. Natürlich wollte ich dabei die Default-Icons als Vector-Drawables haben - hat ein bißchen gedauert, bis ich das gelöst hatte. Hier ist der Lint-Checker für den Code nicht ganz vollständig gewesen. Der Code war frei von entsprechenden Warnungen, ist aber auf alten Geräten gecrasht weil entsprechende Systemfunktionen fehlten …

Apropos Bilder:

Auf den Geräten sind ja meist viele Fotos gespeichert und die Geräte erlauben in zu den Bildern auch die Ortsinformationen zu speichern. Da liegt es nahe, diese Informationen auch zu nutzen und die Bilder dann in der Karte anzuzeigen. Daraus ist das Modul Buddy-Tracker Pictures entstanden.

Dabei entsteht schnell das Problem, daß zu viele Einträge auf die Karte gezeichnet werden müssen. Passenderweise bietet Google für seine Kartenansicht ein Clustering-Modul an, daß diese Einträge zu Gruppen zusammenfassen kann um so mehr Übersicht auf der Karte zu haben. Funktioniert recht performant.

Allerdings hab ich im Moment noch das Problem, daß das Zooming dann noch nicht reibungsfrei funktioniert –> Daher noch in der Beta-Phase.

Mit der Version 0.5. hatte ich das User-Interface gewechselt. Anfangs habe ich das Tab-Layout verwendet. Es machte für mich Sinn, da es nur eine übersichtliche Anzahl von Menüoptionen ab. So konnte man schnell und bequem zwischen den Programmteilen wechseln. Mit der Zeit kamen weitere Fenster hinzu, die nicht prominent genug für einen eigenen Tab waren. Also hatte ich sie im Kontext-Menü geparkt. Zusammen mit den jeweiligen Einträgen für den Kontext des gerade aktiven Tabs wurde das jetzt aber zu unübersichtlich. Mit dem Drawer-Layout sind die verschiedenen Arten von Menüeinträgen jetzt wieder sauber getrennt. Außerdem hab ich etwas Arbeit in das Layout investiert. Ein paar Paddings, etwas Farbe … macht schon einiges aus ;-)

Das Tracking kann über den Activity-Recognition-Service von Google gesteuert werden. So kann Energie gespart werden, wenn das Gerät ruht.

Die App kann über diverse Broadcast-Intents ferngesteuert werden. So kann ein individuelles Verhalten mit Apps wie Tasker, Llama oder Automate hinzu gefügt werden (Tracking nur wenn nicht mit bestimmten WLANs verbunden, nur zu bestimmten Zeiten, …) So könnte man vielleicht so etwas wie einen automatischen Tätigkeitsnachweis oder ein Fahrtenbuch realisieren.

Zu vielen Themen gibt es eine kontextsensitive Hilfe, die eine HTML-Datei aus der App anzeigt. Für die Hilfe ist also keine Internet-Verbindung nötig.

Zu Debugging-Zwecken können die gerade erkannten Aktivitäten auch per Sprachausgabe ausgegeben werden, so daß man nicht dauernd auf das Gerät starren muß um zu wissen ob er gerade trackt oder nicht.

Kommunikationsmethoden Der Buddy-Tracker kann über verschiedene Methoden Kontakt zu den Buddies aufnehmen.

Warum mehr als eine Methode? Jede der Methoden hat ihre Besonderheiten. SMS ist sehr einfach, aber die Kommunikationsmenge ist sehr begrenzt und es kostet Geld. Einen eMail-Account hat jeder, aber nicht jeder will sich seine Inbox mit diesen automatischen Mails zumüllen oder einen separaten Mailaccount dafür anlegen. Shared Folder benötigt Zusatzsoftware, die auch erstmal beherrscht werden will … Außerdem kann ich so mit den Methoden experimentieren und Erfahrungen sammeln, wie praktikabel sie jeweils sind und wo ihre Stärken und Schwächen liegen.

eMail Für die Klartext-Kommunikation mit Buddies, die den Buddy-Tracker nicht installiert haben, nutze ich den entsprechenden Android-Intent, der die jeweilige Default-eMail App startet und den Mailbody mit den entsprechenden Informationen füllt. Dies erfordert keine besonderen Berechtigungen für die App und der Benutzer kann seine gewohnten Kontaktdaten verwenden.

Für die regelmäßige automatische Kommunikation von Buddy-Tracker zu Buddy-Tracker ist diese Methode aber ungeeignet. Hier muß ich alle Mailparameter inclusive des Versands und des Abholens von Mails steuern können. Dies realisiere ich mit den Open-Source Mail-Funktionen aus der JavaX-Library. Die entsprechenden Funktionen werden dann über einen SyncAdapter aufgerufen. Um diesen benutzen zu können muß ich natürlich auch die passenden Account-Services erzeugen und bedienen.

Hier steht ein Update zum Firebase-Network-Manager an, der nochmal erweiterte Konfigurationsoptionen hat. So z.B. das Versenden von Mails abhängig von der jeweils aktuellen Verbindungsart. Könnte ich natürlich aus den System-Intents nachbilden, aber wenn es dafür vorgefertigte Libraries gibt …

Shared Folder Die Idee dahinter ist simpel: Es gibt Software mit der man einen lokalen Ordner und dessen Unterordner übers Internet mit Freunden teilen kann. Die Kommunikation ist dabei verschlüsselt und nur die gewünschten Freunde haben auch Zugriff. Also legt der Buddy-Tracker für sich in diesem Ordner einen Unterordner an, in dem er die zu teilenden Daten dann in Form einzelner kleiner Dateien, die die Nachrichtenart und den Zeitstempel im Dateinamen und die Payload als Dateiinhalt haben, ablegt. Teilt man diesen Ordner dann mit Freunden, so bekommt man dadurch die Unterordner der Freunde auf sein Gerät synchronisiert. Synchronisiert dann die Sharing-Software neue Dateien in diesen Ordner, so bekommt das der Buddy-Tracker über File-Watcher, die in einem Hintergrund-Service gestartet werden, mit und kann die Daten enstprechend verarbeiten.

Allerdings ist man auf die Synchronisationseigenschaften dieser Software angewiesen …

SMS Eigentlch nur der Vollständigkeit halber implementiert, denn alleine der Kostenfaktor schränkt den Einsatz wohl sehr ein. Aber da die Funktion recht einfach umzusetzen ist …

Location-Services Als ich mit dem Buddy-Tracker angefangen habe, gab es zur Positionsbestimmung nur die Location-Services aus dem Paket android.location. Inzwischen propagiert Google seine eigenen Location-Services aus den Google-Play Services. Ich habe sie einem ersten Test unterzogen und für im Moment ungeeignet befunden.

Neben dem asynchronen Aufruf der Funktionen war die Qualität der Positionsdaten nicht ausreichend. Auch wenn man Positionen mit hoher Präzision anfordert liefert dieser Dienst gelegentlich Ergebnisse mit einer Genauigkeit von größer 500 Metern zurück (und zwar reproduzierbar in einigen Gebieten). Und das im zwar im Stadtgebiet aber unter freiem Himmel an Orten an denen die alten Android-Location Services problemlos genaue GPS-Daten liefern.

Abgesehen davon arbeiten die Google-Location Services überwiegend asynchron - man muß sich erst mit dem Google-API-Client mit der Google-API verbinden und kann erst dann seine eigentlichen Abfragen starten. Für die periodische Positionsabfrage im Hintergrund ist das O.K. Aber um zwischendurch mal einzelne Werte abzufragen - insbesondere bei Start der App, sind doch einige Klimmzüge nötig. Will ich aber nur feststellen, ob der User gerade die Location-Services deaktiviert hat um z.B. auf der Karte den Button für die eigenen Position ausblenden zu können, kann ich nicht einfach das System fragen, sondern muß eine asynchrone Anfrage starten und das Ergebnis dann in einem Callback verarbeiten.

Zum Test habe ich die API-Aufrufe in einer Klasse gekapselt. In dieser Klasse gibt es eine weitere abstrakte Klasse, die meine interne private API für den Zugriff auf die Location-Services abstrahiert habe. Von dieser abstrakten Klasse sind dann zwei Klassen (wieder private innere Klassen) abgeleitet, die die jeweiligen APIs konkret umsetzen. So kann ich sogar on the fly die verwendeten Location-Services umschalten und die Ergebnisse miteinander vergleichen.

Des weiteren zeichne ich bei Geräten die einen Luftdrucksensor eingebaut haben, auch den aktuellen Luftdruck bei einer Positionsmessung auf. Aktuell wird das noch nicht weiter ausgewertet, verspricht aber bessere Ergebnisse bei der Bestimmung der Höhenunterschiede auf dem Track da die Luftdruckmessung genauer als die Werte vom GPS sein sollten.

Bemerkungen zu den Programmteilen Karte Die Kartendarstellung verwendet im Moment das Map-Fragment von Google-Maps. Auf dieser Karte werden die aktuellen Positionsdaten der Buddies (ggf. mit ihrer Historie) und die aktiven Orte angezeigt.

Die angezeigten Daten können gefiltert werden. Im Moment habe ich zeitbasierte Filter implementiert. Einmal die Anzeige zwischen Start- und Enddatum und alternativ ein wählbarer Zeitraum von jetzt in die Vergangenheit (die letzten x Stunden …) Dabei kann dann auch noch die Zeitzone eingestellt werden, in der die Zeitangaben angezeigt werden. Praktisch, wenn die beobachteten Positionen weit vom eigenen Standort entfernt sein sollten und man jeweils die dortige lokale Zeit wissen möchte.

Die jeweils angezeigten Track-Daten können hier im GPX-Format eportiert werden oder neue Daten aus GPX-Dateien eingelesen werden. Dabei habe ich (konform zum GPX-Standard) Erweiterungen implementiert um meine zusätzlichen Attribute zu den Positionsdaten auch mit abspeichern und wieder einlesen zu können.

Nette Gimmicks sind:

Kurzer Klick auf einen angezeigten Marker öffnet ein Fenster mit Zusatzinformationen Klick auf dieses Fenster öffnet bei Orts-Markern das entsprechende Fragment mit dem man diese Daten dann bearbeiten kann Bei Positionsmarkern geht es da nicht weiter. Dafür wird aber der zugehörige Track gekennzeichnet indem er mit einer dickeren Strichstärke gezeichnet wird. Hat man auch noch die Option aktiviert die Genauigkeit der Ortsdaten anzuzeigen, wird für jeden Datenpunkt des Tracks ein Marker erzeugt in dessen Info-Window man auf Klick die Details zu diesem Trackpoint bekommt. Außerdem wird die Genauigkeit der Position mit einem entsprechenden Kreis um die Position markiert. Langer Klick auf einen leeren Bereich der Karte erzeugt einen neuen Marker (mit dem festen Namen “Wpt 1”, der dann aber geändert werden kann (s.o.)) Langer Klick auf einen Ortsmarker bringt diesen in den Drag-and-Drop Mode wo man ihn dann beliebig auf der Karte verschieben kann.

Eine einfache Navigation zum Zielort ohne Straßenrouting, also geeignet für die offene See oder wenn man in der Natur ohne Straße querfeldein wandert. Aus dem eigenen Standort und den Zielkoordinaten wird Entferung und Richtung zum Ziel bestimmt. Dies wird mit einem selbst geschriebenen View, der ein HSI-Instrument (Horizontal Situation Indicator) aus der Fliegerei nachbildet visualisiert und zusätzlich werden die Zahlenwerte diverser Eigenschaften noch aufgelistet.

Aufgrund der hohen Update-Frequenz darf ich mir hier keine Memory-Leaks erlauben und muß auf die Performance achten. Ist mir gelugen, denn dieses Fragment läuft auch auf Uralt-Geräten (damals noch mit Android 2.2)

Orte, Buddies, Teilen

Keine großen Besonderheiten. Es werden Listviews mit Mehrfachauswahl und Action-Menüs eingesetzt.

ToDo

Android Wear

Bei aktivem Navigationsmodus Informationen zum aktuellen Ziel (Richtung, Entfernung, TTG, ETA) und zum aktiellen eigenen Bewegungszustand (SOG, COG) anzeigen. Anzeige auch im Ambient-Modus, allerdings mit reduziertem Detailreichtum.

Android N

Anpassen an die diversen Änderungen in der neuen Version von Android.