|
| 1 | +package org.microg.locationtest |
| 2 | + |
| 3 | +import android.Manifest |
| 4 | +import android.content.pm.PackageManager |
| 5 | +import android.graphics.Color |
| 6 | +import android.location.Location |
| 7 | +import android.location.LocationListener |
| 8 | +import android.location.LocationManager |
| 9 | +import android.os.Bundle |
| 10 | +import android.os.Handler |
| 11 | +import android.os.Looper |
| 12 | +import android.os.SystemClock |
| 13 | +import android.widget.TextView |
| 14 | +import androidx.appcompat.app.AppCompatActivity |
| 15 | +import androidx.core.app.ActivityCompat |
| 16 | +import androidx.core.content.ContextCompat |
| 17 | +import androidx.core.content.getSystemService |
| 18 | +import org.osmdroid.config.Configuration |
| 19 | +import org.osmdroid.tileprovider.tilesource.TileSourceFactory |
| 20 | +import org.osmdroid.util.GeoPoint |
| 21 | +import org.osmdroid.views.MapView |
| 22 | +import org.osmdroid.views.overlay.Marker |
| 23 | +import org.osmdroid.views.overlay.Polygon |
| 24 | + |
| 25 | +class MainActivity : AppCompatActivity() { |
| 26 | + |
| 27 | + private lateinit var mapBug: MapView |
| 28 | + private lateinit var mapFix: MapView |
| 29 | + private lateinit var tvBugInfo: TextView |
| 30 | + private lateinit var tvFixInfo: TextView |
| 31 | + private lateinit var tvStatus: TextView |
| 32 | + |
| 33 | + private lateinit var locationManager: LocationManager |
| 34 | + |
| 35 | + private val miniLLC = MiniLastLocationCapsule() |
| 36 | + |
| 37 | + private val handler = Handler(Looper.getMainLooper()) |
| 38 | + private val tickRunnable = object : Runnable { |
| 39 | + override fun run() { |
| 40 | + refreshPanels() |
| 41 | + handler.postDelayed(this, 500) |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + // GPS → miniLLC.updateFineLocation (mirrors GmsCore) |
| 46 | + private val gpsListener = LocationListener { location -> |
| 47 | + miniLLC.updateFineLocation(location) |
| 48 | + refreshPanels() |
| 49 | + } |
| 50 | + |
| 51 | + // Network → miniLLC.updateCoarseLocation only (mirrors GmsCore) |
| 52 | + private val networkListener = LocationListener { location -> |
| 53 | + miniLLC.updateCoarseLocation(location) |
| 54 | + refreshPanels() |
| 55 | + } |
| 56 | + |
| 57 | + override fun onCreate(savedInstanceState: Bundle?) { |
| 58 | + super.onCreate(savedInstanceState) |
| 59 | + Configuration.getInstance().userAgentValue = packageName |
| 60 | + setContentView(R.layout.activity_main) |
| 61 | + |
| 62 | + mapBug = findViewById(R.id.mapCoarse) |
| 63 | + mapFix = findViewById(R.id.mapFine) |
| 64 | + tvBugInfo = findViewById(R.id.tvCoarseInfo) |
| 65 | + tvFixInfo = findViewById(R.id.tvFineInfo) |
| 66 | + tvStatus = findViewById(R.id.tvStatus) |
| 67 | + |
| 68 | + // Relabel panels |
| 69 | + findViewById<TextView>(R.id.labelCoarse).text = "getLocation(FINE) → lastCoarseLocation [BUG]" |
| 70 | + findViewById<TextView>(R.id.labelFine).text = "getLocation(FINE) → lastFineLocation [FIX]" |
| 71 | + |
| 72 | + setupMap(mapBug) |
| 73 | + setupMap(mapFix) |
| 74 | + |
| 75 | + locationManager = getSystemService()!! |
| 76 | + |
| 77 | + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) |
| 78 | + != PackageManager.PERMISSION_GRANTED |
| 79 | + ) { |
| 80 | + ActivityCompat.requestPermissions( |
| 81 | + this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1 |
| 82 | + ) |
| 83 | + } else { |
| 84 | + startUpdates() |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + private fun setupMap(map: MapView) { |
| 89 | + map.setTileSource(TileSourceFactory.MAPNIK) |
| 90 | + map.setMultiTouchControls(true) |
| 91 | + map.controller.setZoom(15.0) |
| 92 | + } |
| 93 | + |
| 94 | + override fun onRequestPermissionsResult( |
| 95 | + requestCode: Int, permissions: Array<out String>, grantResults: IntArray |
| 96 | + ) { |
| 97 | + super.onRequestPermissionsResult(requestCode, permissions, grantResults) |
| 98 | + if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) startUpdates() |
| 99 | + else tvStatus.text = "Permission denied" |
| 100 | + } |
| 101 | + |
| 102 | + @Suppress("MissingPermission") |
| 103 | + private fun startUpdates() { |
| 104 | + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 0f, gpsListener) |
| 105 | + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000L, 0f, networkListener) |
| 106 | + handler.post(tickRunnable) |
| 107 | + } |
| 108 | + |
| 109 | + private fun refreshPanels() { |
| 110 | + val bugLocation = miniLLC.getLocationBuggy() |
| 111 | + val fixLocation = miniLLC.getLocationFixed() |
| 112 | + |
| 113 | + if (bugLocation != null) { |
| 114 | + updateMap(mapBug, bugLocation, "#220066FF", "#990066FF") |
| 115 | + updateInfo(tvBugInfo, bugLocation) |
| 116 | + } |
| 117 | + if (fixLocation != null) { |
| 118 | + updateMap(mapFix, fixLocation, "#2200AA00", "#9900AA00") |
| 119 | + updateInfo(tvFixInfo, fixLocation) |
| 120 | + } |
| 121 | + |
| 122 | + updateStatus(bugLocation, fixLocation) |
| 123 | + } |
| 124 | + |
| 125 | + private fun updateMap(map: MapView, loc: Location, fillHex: String, strokeHex: String) { |
| 126 | + val center = GeoPoint(loc.latitude, loc.longitude) |
| 127 | + map.overlays.clear() |
| 128 | + |
| 129 | + if (loc.accuracy > 0f) { |
| 130 | + val circle = Polygon(map).apply { |
| 131 | + points = Polygon.pointsAsCircle(center, loc.accuracy.toDouble()) |
| 132 | + fillColor = Color.parseColor(fillHex) |
| 133 | + strokeColor = Color.parseColor(strokeHex) |
| 134 | + strokeWidth = 2f |
| 135 | + } |
| 136 | + map.overlays.add(circle) |
| 137 | + } |
| 138 | + |
| 139 | + val marker = Marker(map).apply { |
| 140 | + position = center |
| 141 | + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER) |
| 142 | + title = "${loc.provider} ${loc.accuracy.toInt()} m" |
| 143 | + } |
| 144 | + map.overlays.add(marker) |
| 145 | + |
| 146 | + map.controller.setCenter(center) |
| 147 | + if (map.zoomLevelDouble < 13.0) map.controller.setZoom(15.0) |
| 148 | + map.invalidate() |
| 149 | + } |
| 150 | + |
| 151 | + private fun updateInfo(tv: TextView, loc: Location) { |
| 152 | + val ageMs = (SystemClock.elapsedRealtimeNanos() - loc.elapsedRealtimeNanos) / 1_000_000 |
| 153 | + tv.text = "${String.format("%.6f", loc.latitude)}, ${String.format("%.6f", loc.longitude)}" + |
| 154 | + " | ${loc.accuracy.toInt()} m | provider: ${loc.provider} | ${ageMs / 1000.0}s ago" |
| 155 | + } |
| 156 | + |
| 157 | + private fun updateStatus(bug: Location?, fix: Location?) { |
| 158 | + if (bug == null && fix == null) { |
| 159 | + tvStatus.text = "Waiting for location... (go outdoors for GPS)" |
| 160 | + tvStatus.setBackgroundColor(Color.parseColor("#212121")) |
| 161 | + return |
| 162 | + } |
| 163 | + |
| 164 | + val bugAge = bug?.let { (SystemClock.elapsedRealtimeNanos() - it.elapsedRealtimeNanos) / 1_000_000_000.0 } |
| 165 | + val fixAge = fix?.let { (SystemClock.elapsedRealtimeNanos() - it.elapsedRealtimeNanos) / 1_000_000_000.0 } |
| 166 | + |
| 167 | + val hasBug = fix != null && bug != null && bug.provider != "gps" && (fix.provider == "gps") |
| 168 | + |
| 169 | + tvStatus.text = buildString { |
| 170 | + append("bug: ${bug?.accuracy?.toInt() ?: "?"}m (${bug?.provider}) ") |
| 171 | + append("fix: ${fix?.accuracy?.toInt() ?: "?"}m (${fix?.provider})\n") |
| 172 | + if (hasBug) { |
| 173 | + append("BUG CONFIRMED: fix panel got GPS (${fix!!.accuracy.toInt()}m) but bug panel still shows network") |
| 174 | + } else if (fix?.provider == "gps" && bug?.provider == "gps") { |
| 175 | + append("Both have GPS fix — bug not visible yet (network fix hasn't arrived)") |
| 176 | + } else { |
| 177 | + append("%.1fs since bug update | %.1fs since fix update".format(bugAge ?: 0.0, fixAge ?: 0.0)) |
| 178 | + } |
| 179 | + } |
| 180 | + tvStatus.setBackgroundColor(if (hasBug) Color.parseColor("#B71C1C") else Color.parseColor("#212121")) |
| 181 | + } |
| 182 | + |
| 183 | + override fun onResume() { super.onResume(); mapBug.onResume(); mapFix.onResume() } |
| 184 | + override fun onPause() { super.onPause(); mapBug.onPause(); mapFix.onPause() } |
| 185 | + |
| 186 | + override fun onDestroy() { |
| 187 | + super.onDestroy() |
| 188 | + handler.removeCallbacks(tickRunnable) |
| 189 | + locationManager.removeUpdates(gpsListener) |
| 190 | + locationManager.removeUpdates(networkListener) |
| 191 | + } |
| 192 | +} |
0 commit comments