1+ package de.storchp.opentracks.osmplugin.dashboardapi
2+
3+ import android.content.ContentResolver
4+ import android.content.Intent
5+ import android.database.ContentObserver
6+ import android.net.Uri
7+ import android.os.Handler
8+ import android.util.Log
9+ import de.storchp.opentracks.osmplugin.map.DEFAULT_TRACK_COLOR_MORE
10+ import de.storchp.opentracks.osmplugin.map.MapData
11+ import de.storchp.opentracks.osmplugin.map.MapUtils
12+ import de.storchp.opentracks.osmplugin.map.StyleColorCreator
13+ import de.storchp.opentracks.osmplugin.map.TrackColorMode
14+ import de.storchp.opentracks.osmplugin.map.TrackStatistics
15+ import de.storchp.opentracks.osmplugin.utils.PreferencesUtils
16+ import de.storchp.opentracks.osmplugin.utils.TrackpointsDebug
17+ import org.oscim.core.BoundingBox
18+ import org.oscim.core.GeoPoint
19+ import java.lang.Exception
20+
21+ private val TAG : String = DashboardReader ::class .java.getSimpleName()
22+
23+ private const val EXTRAS_PROTOCOL_VERSION = " PROTOCOL_VERSION"
24+ private const val EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK =
25+ " EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK"
26+ private const val EXTRAS_SHOULD_KEEP_SCREEN_ON = " EXTRAS_SHOULD_KEEP_SCREEN_ON"
27+ private const val EXTRAS_SHOW_WHEN_LOCKED = " EXTRAS_SHOULD_KEEP_SCREEN_ON"
28+ private const val EXTRAS_SHOW_FULLSCREEN = " EXTRAS_SHOULD_FULLSCREEN"
29+
30+ typealias UpdateTrackStatistics = (TrackStatistics ? ) -> Unit
31+ typealias UpdateTrackpointsDebug = (TrackpointsDebug ) -> Unit
32+
33+ class DashboardReader (
34+ intent : Intent ,
35+ private val contentResolver : ContentResolver ,
36+ private val mapData : MapData ,
37+ private val updateTrackStatistics : UpdateTrackStatistics ,
38+ private val updateTrackpointsDebug : UpdateTrackpointsDebug ,
39+ ) {
40+
41+ private val colorCreator = StyleColorCreator ()
42+ private var trackColor = colorCreator.nextColor()
43+ private var trackpointsDebug = TrackpointsDebug ()
44+ private var lastWaypointId = 0L
45+ private var lastTrackPointId = 0L
46+ private var contentObserver: OpenTracksContentObserver ? = null
47+ var lastTrackId = 0L
48+ val keepScreenOn: Boolean
49+ val showOnLockScreen: Boolean
50+ val showFullscreen: Boolean
51+ val isOpenTracksRecordingThisTrack: Boolean
52+ val tracksUri: Uri
53+ val trackpointsUri: Uri
54+ val waypointsUri: Uri ?
55+ val protocolVersion: Int
56+
57+ init {
58+ require(intent.isDashboardAction())
59+ val uris =
60+ intent.getParcelableArrayListExtra<Uri >(APIConstants .ACTION_DASHBOARD_PAYLOAD )!!
61+ protocolVersion = intent.getIntExtra(EXTRAS_PROTOCOL_VERSION , 1 )
62+ tracksUri = APIConstants .getTracksUri(uris)
63+ trackpointsUri = APIConstants .getTrackpointsUri(uris)
64+ waypointsUri = APIConstants .getWaypointsUri(uris)
65+ keepScreenOn = intent.getBooleanExtra(EXTRAS_SHOULD_KEEP_SCREEN_ON , false )
66+ showOnLockScreen = intent.getBooleanExtra(EXTRAS_SHOW_WHEN_LOCKED , false )
67+ showFullscreen = intent.getBooleanExtra(EXTRAS_SHOW_FULLSCREEN , false )
68+ isOpenTracksRecordingThisTrack =
69+ intent.getBooleanExtra(EXTRAS_OPENTRACKS_IS_RECORDING_THIS_TRACK , false )
70+
71+ trackpointsDebug.protocolVersion = protocolVersion
72+ readTrackpoints(trackpointsUri, false , protocolVersion)
73+ readTracks(tracksUri)
74+ waypointsUri?.let { readWaypoints(it) }
75+ }
76+
77+ fun readTrackpoints (data : Uri , update : Boolean , protocolVersion : Int ) {
78+ Log .i(TAG , " Loading trackpoints from $data " )
79+
80+ val showPauseMarkers = PreferencesUtils .isShowPauseMarkers()
81+ val latLongs = mutableListOf<GeoPoint >()
82+ val tolerance = PreferencesUtils .getTrackSmoothingTolerance()
83+
84+ try {
85+ val trackpointsBySegments: TrackpointsBySegments =
86+ TrackpointReader .readTrackpointsBySegments(
87+ contentResolver,
88+ data,
89+ lastTrackPointId,
90+ protocolVersion
91+ )
92+ if (trackpointsBySegments.isEmpty()) {
93+ Log .d(TAG , " No new trackpoints received" )
94+ return
95+ }
96+
97+ val average = trackpointsBySegments.calcAverageSpeed()
98+ val maxSpeed = trackpointsBySegments.calcMaxSpeed()
99+ val averageToMaxSpeed = maxSpeed - average
100+ var trackColorMode = PreferencesUtils .getTrackColorMode()
101+ if (isOpenTracksRecordingThisTrack && ! trackColorMode.supportsLiveTrack) {
102+ trackColorMode = DEFAULT_TRACK_COLOR_MORE
103+ }
104+
105+ trackpointsBySegments.segments.map { trackpoints ->
106+ if (! update) {
107+ mapData.polyline = null // cut polyline on new segment
108+ if (tolerance > 0 ) { // smooth track
109+ return @map MapUtils .decimate(tolerance, trackpoints)
110+ }
111+ }
112+ return @map trackpoints
113+ }.forEach { trackpoints ->
114+ trackpoints.filter { it.latLong != null }.forEach { trackpoint ->
115+ lastTrackPointId = trackpoint.id
116+
117+ if (trackpoint.trackId != lastTrackId) {
118+ if (trackColorMode == TrackColorMode .BY_TRACK ) {
119+ trackColor = colorCreator.nextColor()
120+ }
121+ lastTrackId = trackpoint.trackId
122+ mapData.resetCurrentPolyline()
123+ }
124+
125+ if (trackColorMode == TrackColorMode .BY_SPEED ) {
126+ trackColor = MapUtils .getTrackColorBySpeed(
127+ average,
128+ averageToMaxSpeed,
129+ trackpoint
130+ )
131+ mapData.addNewPolyline(trackColor, trackpoint.latLong!! )
132+ } else {
133+ mapData.extendPolyline(trackColor, trackpoint.latLong!! )
134+ }
135+
136+
137+ if (trackpoint.isPause && showPauseMarkers) {
138+ mapData.addPauseMarker(trackpoint.latLong)
139+ }
140+
141+ if (! update) {
142+ mapData.endPos?.let { latLongs.add(it) }
143+ }
144+
145+
146+ }
147+ trackpointsBySegments.debug.trackpointsDrawn + = trackpoints.size
148+ }
149+ trackpointsDebug.add(trackpointsBySegments.debug)
150+ } catch (e: Exception ) {
151+ throw RuntimeException (" Error reading trackpoints" , e)
152+ }
153+
154+ mapData.setEndMarker()
155+
156+ var myPos: GeoPoint ? = null
157+ if (update && mapData.endPos != null ) {
158+ myPos = mapData.endPos
159+ mapData.renderMap()
160+ } else if (latLongs.isNotEmpty()) {
161+ mapData.boundingBox = BoundingBox (latLongs).extendMargin(1.2f ).also {
162+ myPos = it.getCenterPoint()
163+ }
164+ }
165+
166+ if (myPos != null ) {
167+ mapData.updateMapPositionAndRotation(myPos)
168+ }
169+
170+ updateTrackpointsDebug(trackpointsDebug)
171+ }
172+
173+ private fun readWaypoints (data : Uri ) {
174+ try {
175+ WaypointReader .readWaypoints(contentResolver, data, lastWaypointId).forEach {
176+ lastWaypointId = it.id
177+ mapData.addWaypointMarker(it)
178+ }
179+ } catch (e: Exception ) {
180+ Log .e(TAG , " Reading waypoints failed" , e)
181+ }
182+ }
183+
184+ private fun readTracks (data : Uri ) {
185+ val tracks = TrackReader .readTracks(contentResolver, data)
186+ val trackStatistics = if (tracks.isNotEmpty()) TrackStatistics (tracks) else null
187+ updateTrackStatistics(trackStatistics)
188+ }
189+
190+ fun hasTrackId () = lastTrackId > 0
191+
192+ fun startContentObserver () {
193+ contentObserver = OpenTracksContentObserver (
194+ tracksUri,
195+ trackpointsUri,
196+ waypointsUri,
197+ protocolVersion
198+ )
199+
200+ contentResolver.registerContentObserver(tracksUri, false , contentObserver!! )
201+ contentResolver.registerContentObserver(
202+ trackpointsUri,
203+ false ,
204+ contentObserver!!
205+ )
206+ if (waypointsUri != null ) {
207+ contentResolver.registerContentObserver(
208+ waypointsUri,
209+ false ,
210+ contentObserver!!
211+ )
212+ }
213+
214+ }
215+
216+ fun unregisterContentObserver () {
217+ contentObserver?.let { contentResolver.unregisterContentObserver(it) }
218+ contentObserver = null
219+ }
220+
221+ private inner class OpenTracksContentObserver (
222+ private val tracksUri : Uri ,
223+ private val trackpointsUri : Uri ,
224+ private val waypointsUri : Uri ? ,
225+ private val protocolVersion : Int
226+ ) : ContentObserver(Handler ()) {
227+
228+ override fun onChange (selfChange : Boolean , uri : Uri ? ) {
229+ if (uri == null ) {
230+ return // nothing can be done without an uri
231+ }
232+ if (tracksUri.toString().startsWith(uri.toString())) {
233+ readTracks(tracksUri)
234+ } else if (trackpointsUri.toString().startsWith(uri.toString())) {
235+ readTrackpoints(trackpointsUri, true , protocolVersion)
236+ } else if (waypointsUri?.toString()?.startsWith(uri.toString()) == true ) {
237+ readWaypoints(waypointsUri)
238+ }
239+ }
240+ }
241+
242+ }
243+
244+ fun Intent.isDashboardAction (): Boolean {
245+ return APIConstants .ACTION_DASHBOARD == action
246+ }
0 commit comments