summaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/net/rctt/netmon/Cell.kt122
-rw-r--r--app/src/main/java/net/rctt/netmon/CellDb.kt51
-rw-r--r--app/src/main/java/net/rctt/netmon/CellDbItem.kt31
-rw-r--r--app/src/main/java/net/rctt/netmon/CellLogger.kt23
-rw-r--r--app/src/main/java/net/rctt/netmon/CellView.kt91
-rw-r--r--app/src/main/java/net/rctt/netmon/LogFragment.kt59
-rw-r--r--app/src/main/java/net/rctt/netmon/MainActivity.kt135
-rw-r--r--app/src/main/java/net/rctt/netmon/MainFragment.kt59
-rw-r--r--app/src/main/java/net/rctt/netmon/MapFragment.kt79
-rw-r--r--app/src/main/java/net/rctt/netmon/ScanFragment.kt63
-rw-r--r--app/src/main/java/net/rctt/netmon/ScannerService.kt55
11 files changed, 499 insertions, 269 deletions
diff --git a/app/src/main/java/net/rctt/netmon/Cell.kt b/app/src/main/java/net/rctt/netmon/Cell.kt
new file mode 100644
index 0000000..951c79b
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/Cell.kt
@@ -0,0 +1,122 @@
+package net.rctt.netmon
+
+import android.telephony.CellIdentity
+import android.telephony.CellIdentityNr
+import android.telephony.CellInfo
+import android.telephony.CellInfoGsm
+import android.telephony.CellInfoLte
+import android.telephony.CellInfoNr
+import android.telephony.CellInfoTdscdma
+import android.telephony.CellInfoWcdma
+
+// NETWORK_TYPE;MCC;MNC;LAC;CID;PSC;CHANNEL;LATITUDE;LONGITUDE;ACCURACY;DESCRIPTION
+
+/*
+NETWORK_TYPE String C (CDMA), G (GSM), W (WCDMA), T (TDSCDMA), L (LTE), N (5G NR) Mandatory for CDMA
+MCC String 3 digits Mandatory
+MNC String 2 or 3 digits Mandatory
+LAC Int NID (CDMA), LAC (GSM, WCDMA, TDSCDMA), TAC (LTE, 5G NR) Mandatory
+CID Long BID (CDMA), CID (GSM, WCDMA, TDSCDMA), CI (LTE), NCI (5G NR) Mandatory
+PSC Int SID (CDMA), BSIC (GSM), PSC (WCDMA), CPID (TDSCDMA), PCI (LTE, 5G NR) Mandatory for CDMA
+CHANNEL Int ARFCN (GSM, 5G NR), UARFCN (WCDMA, TDSCDMA), EARFCN (LTE)
+LATITUDE Double [-90;90], . as separator
+LONGITUDE Double [-180;180], . as separator
+ACCURACY Int in meters
+DESCRIPTION String text
+*/
+
+class CellDbItem {
+ var Type: String = ""
+ var MCC: String = ""
+ var MNC: String = ""
+ var LAC: Int = 0
+ var CID: Long = 0
+ var PSC: Int = 0
+ var Channel: Int = 0
+ var Latitude: Double = 0.0
+ var Longitude: Double = 0.0
+ var Accuracy: Int = 0
+ var Description: String = ""
+}
+
+class CellLogItem {
+ var type: String = ""
+ var lac: Int = 0
+ var cid: Long = 0
+
+ var description: String = "Unknown"
+ var latitude: Double = 0.0
+ var longitude: Double = 0.0
+
+ var power: Number = 0
+ var id: String = ""
+ val powerHistory: MutableList<Number> = mutableListOf()
+
+ var timestamp: String = ""
+ var userLat: Double = 0.0
+ var userLon: Double = 0.0
+
+ constructor(cell: CellInfo) {
+ when (cell) {
+ is CellInfoGsm -> {
+ type = "GSM"
+ cid = cell.cellIdentity.cid.toLong()
+ lac = cell.cellIdentity.lac
+ }
+
+ is CellInfoLte -> {
+ type = "LTE"
+ cid = cell.cellIdentity.ci.toLong()
+ lac = cell.cellIdentity.tac
+ }
+
+ is CellInfoNr -> {
+ type = "NR"
+ val id = cell.cellIdentity as CellIdentityNr
+ cid = id.nci
+ lac = id.tac
+ }
+
+ is CellInfoTdscdma -> {
+ type = "TDSCMA"
+ cid = cell.cellIdentity.cid.toLong()
+ lac = cell.cellIdentity.lac
+ }
+
+ is CellInfoWcdma -> {
+ type = "WCDMA "
+ cid = cell.cellIdentity.cid.toLong()
+ lac = cell.cellIdentity.lac
+ }
+ }
+
+ id = "$lac.$cid"
+ repeat(20) {
+ powerHistory.add(-100)
+ }
+ }
+
+ fun update(cell: CellInfo) {
+ power = cell.cellSignalStrength.dbm
+ powerHistory.removeAt(0)
+ powerHistory.add(power)
+
+ timestamp = System.currentTimeMillis().toString()
+ }
+
+ fun valid(): Boolean {
+ return lac != CellInfo.UNAVAILABLE
+ }
+
+ fun serialize(): List<String> {
+ return listOf(
+ timestamp,
+ userLat.toString(),
+ userLon.toString(),
+ type,
+ lac.toString(),
+ cid.toString(),
+ power.toString(),
+ )
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/CellDb.kt b/app/src/main/java/net/rctt/netmon/CellDb.kt
new file mode 100644
index 0000000..7c377d5
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/CellDb.kt
@@ -0,0 +1,51 @@
+package net.rctt.netmon
+
+import java.io.File
+import com.jsoizo.kotlincsv.CsvDialect
+import com.jsoizo.kotlincsv.csvReader
+import com.jsoizo.kotlincsv.reader.readFromFile
+
+class CellDb {
+ val db = HashMap<Int, HashMap<Long, CellDbItem>>()
+
+ constructor() {
+ val reader = csvReader {
+ dialect = CsvDialect(delimiter = ';')
+ }
+
+ reader.readFromFile(File("/storage/emulated/0/cells.csv")) { rows ->
+ rows.forEach {
+ val item = CellDbItem()
+ item.Type = it.elementAt(0)
+ item.MCC = it.elementAt(1)
+ item.MNC = it.elementAt(2)
+ item.LAC = it.elementAt(3).toInt()
+ item.CID = it.elementAt(4).toLong()
+ if (it.elementAt(5) != "") {
+ item.PSC = it.elementAt(5).toInt()
+ }
+ if (it.elementAt(6) != "") {
+ item.Channel = it.elementAt(6).toInt()
+ }
+ item.Latitude = it.elementAt(7).toDouble()
+ item.Longitude = it.elementAt(8).toDouble()
+ if (it.elementAt(9) != "") {
+ item.Accuracy = it.elementAt(9).toInt()
+ }
+ item.Description = it.elementAt(10)
+
+ var lac = db[item.LAC]
+ if (lac == null) {
+ db[item.LAC] = HashMap()
+ lac = db[item.LAC]
+ }
+
+ lac?.set(item.CID, item)
+ }
+ }
+ }
+
+ fun get(lac: Int, cid: Long): CellDbItem? {
+ return db[lac]?.get(cid)
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/CellDbItem.kt b/app/src/main/java/net/rctt/netmon/CellDbItem.kt
deleted file mode 100644
index 2922bb1..0000000
--- a/app/src/main/java/net/rctt/netmon/CellDbItem.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package net.rctt.netmon
-
-// NETWORK_TYPE;MCC;MNC;LAC;CID;PSC;CHANNEL;LATITUDE;LONGITUDE;ACCURACY;DESCRIPTION
-
-/*
-NETWORK_TYPE String C (CDMA), G (GSM), W (WCDMA), T (TDSCDMA), L (LTE), N (5G NR) Mandatory for CDMA
-MCC String 3 digits Mandatory
-MNC String 2 or 3 digits Mandatory
-LAC Int NID (CDMA), LAC (GSM, WCDMA, TDSCDMA), TAC (LTE, 5G NR) Mandatory
-CID Long BID (CDMA), CID (GSM, WCDMA, TDSCDMA), CI (LTE), NCI (5G NR) Mandatory
-PSC Int SID (CDMA), BSIC (GSM), PSC (WCDMA), CPID (TDSCDMA), PCI (LTE, 5G NR) Mandatory for CDMA
-CHANNEL Int ARFCN (GSM, 5G NR), UARFCN (WCDMA, TDSCDMA), EARFCN (LTE)
-LATITUDE Double [-90;90], . as separator
-LONGITUDE Double [-180;180], . as separator
-ACCURACY Int in meters
-DESCRIPTION String text
-*/
-
-class CellDbItem {
- var Type: String = ""
- var MCC: String = ""
- var MNC: String = ""
- var LAC: Int = 0
- var CID: Long = 0
- var PSC: Int = 0
- var Channel: Int = 0
- var Latitude: Double = 0.0
- var Longitude: Double = 0.0
- var Accuracy: Int = 0
- var Description: String = ""
-} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/CellLogger.kt b/app/src/main/java/net/rctt/netmon/CellLogger.kt
new file mode 100644
index 0000000..dadf3c9
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/CellLogger.kt
@@ -0,0 +1,23 @@
+package net.rctt.netmon
+
+import com.jsoizo.kotlincsv.writer.CsvWriter
+import java.io.FileOutputStream
+import java.io.OutputStreamWriter
+
+class CellLogger {
+ val writer: CsvWriter = CsvWriter()
+ var file: FileOutputStream = FileOutputStream("/storage/emulated/0/log.csv", true)
+ var output: OutputStreamWriter = OutputStreamWriter(file)
+
+ fun write(cells: List<CellLogItem>) {
+ val strings = mutableListOf<List<String>>()
+
+ cells.forEach { cell ->
+ strings.add(cell.serialize())
+ }
+
+ val csv = writer.writeAll(strings)
+ output.write(csv)
+ output.flush()
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/CellView.kt b/app/src/main/java/net/rctt/netmon/CellView.kt
index 55668c3..1d60a21 100644
--- a/app/src/main/java/net/rctt/netmon/CellView.kt
+++ b/app/src/main/java/net/rctt/netmon/CellView.kt
@@ -7,6 +7,7 @@ import android.telephony.CellInfoLte
import android.telephony.CellInfoNr
import android.telephony.CellInfoTdscdma
import android.telephony.CellInfoWcdma
+import android.util.Log
import android.view.LayoutInflater
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -16,96 +17,46 @@ import com.androidplot.xy.XYPlot
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.overlay.Marker
-class CellView : ConstraintLayout{
- var cellId: Number
- var power: Int
- var powerHistory: MutableList<Number>
+class CellView : ConstraintLayout {
var typeView: TextView
- var idView: TextView
+ var lacView: TextView
+ var cidView: TextView
var powerView: TextView
var descView: TextView
var powerChartView: XYPlot
var powerChartSeries: SimpleXYSeries
- lateinit var mapMarker: Marker
-
- constructor(ctx: Context, id: Number) : super(ctx) {
- cellId = id
- power = 0
-
+ constructor(ctx: Context, cell: CellLogItem) : super(ctx) {
LayoutInflater.from(context).inflate(R.layout.cell_view, this)
typeView = findViewById(R.id.type)
- idView = findViewById(R.id.id)
+ lacView = findViewById(R.id.lac)
+ cidView = findViewById(R.id.cid)
powerView = findViewById(R.id.power)
descView = findViewById(R.id.desc)
powerChartView = findViewById(R.id.power_chart)
- powerHistory = mutableListOf()
- powerChartSeries= SimpleXYSeries(
- powerHistory,
+ powerChartSeries = SimpleXYSeries(
+ cell.powerHistory,
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY,
"Series"
)
- populatePowerChart()
-
- idView.text = cellId.toString()
- }
-
- fun set(cell: CellInfoGsm){
- power = cell.cellSignalStrength.dbm
- typeView.text = "GSM"
- powerView.text = power.toString()
- }
-
- fun set(cell: CellInfoLte){
- power = cell.cellSignalStrength.dbm
- typeView.text = "LTE"
- powerView.text = power.toString()
- }
- fun set(cell: CellInfoNr){
- power = cell.cellSignalStrength.dbm
- typeView.text = "NR"
- powerView.text = power.toString()
+ typeView.text = cell.type
+ lacView.text = cell.lac.toString()
+ cidView.text = cell.cid.toString()
+ powerView.text = cell.power.toString()
+ descView.text = cell.description
}
- fun set(cell: CellInfoTdscdma){
- power = cell.cellSignalStrength.dbm
- typeView.text = "TDSCDMA"
- powerView.text = power.toString()
- }
-
- fun set(cell: CellInfoWcdma){
- power = cell.cellSignalStrength.dbm
- typeView.text = "WCDMA"
- powerView.text = power.toString()
- }
-
- fun setDesc(desc: String) {
- descView.text = desc
- }
-
- fun setMarker(m: Marker){
- mapMarker = m
- }
-
- fun setLocation(lat: Double, lon: Double) {
- mapMarker.setPosition(GeoPoint(lat, lon))
- mapMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
- mapMarker.setDefaultIcon()
- mapMarker.title = descView.text.toString()
- }
-
- fun refresh() {
+ fun refresh(cell: CellLogItem) {
+ powerView.text = cell.power.toString()
powerChartView.removeSeries(powerChartSeries)
- powerHistory.removeAt(1)
- powerHistory.add(power)
val seriesData = mutableListOf<Number>()
- for (p in powerHistory) {
+ for (p in cell.powerHistory) {
seriesData.add(p)
}
@@ -116,13 +67,7 @@ class CellView : ConstraintLayout{
)
val series1Format = LineAndPointFormatter(Color.RED, Color.GREEN, Color.BLUE, null)
-
powerChartView.addSeries(powerChartSeries, series1Format)
- }
-
- fun populatePowerChart() {
- repeat(20) {
- powerHistory.add(-100)
- }
+ powerChartView.redraw()
}
}
diff --git a/app/src/main/java/net/rctt/netmon/LogFragment.kt b/app/src/main/java/net/rctt/netmon/LogFragment.kt
deleted file mode 100644
index 3755b0a..0000000
--- a/app/src/main/java/net/rctt/netmon/LogFragment.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package net.rctt.netmon
-
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [LogFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
-class LogFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_log, container, false)
- }
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment LogFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- LogFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/MainActivity.kt b/app/src/main/java/net/rctt/netmon/MainActivity.kt
index 5a25086..6cfd838 100644
--- a/app/src/main/java/net/rctt/netmon/MainActivity.kt
+++ b/app/src/main/java/net/rctt/netmon/MainActivity.kt
@@ -1,32 +1,153 @@
package net.rctt.netmon
+import android.Manifest
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
+import android.location.Location
+import android.location.LocationManager
import android.os.Bundle
-import androidx.appcompat.app/**/.AppCompatActivity
+import android.os.IBinder
+import android.telephony.CellInfo
+import androidx.annotation.RequiresPermission
+import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationView
+import org.osmdroid.config.Configuration
+
class MainActivity : AppCompatActivity() {
+ val cellDb = CellDb()
+ val cellLogger = CellLogger()
+ val currentCells: HashMap<String, CellLogItem> = hashMapOf()
- lateinit var bottomNav : BottomNavigationView
+ lateinit var bottomNav: BottomNavigationView
+ lateinit var scannerService: ScannerService
+ var scannerServiceBound: Boolean = false
+ var scanFragment: ScanFragment? = null
+ var mapFragment: MapFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ Configuration.getInstance().userAgentValue =
+ "Netmon/0.1 (+https://rctt.net; contact: bt@rctt.net)"
+
setContentView(R.layout.activity_main)
- loadFragment(MainFragment())
+ loadFragment(ScanFragment())
bottomNav = findViewById(R.id.bottomNav)!!
+
bottomNav.setOnItemSelectedListener {
when (it.itemId) {
- R.id.main -> loadFragment(MainFragment())
- R.id.log -> loadFragment(LogFragment())
+ R.id.main -> loadFragment(ScanFragment())
R.id.map -> loadFragment(MapFragment())
}
true
}
}
- private fun loadFragment(fragment: Fragment){
+
+ override fun onStart() {
+ super.onStart()
+ val intentBind = Intent(this, ScannerService::class.java)
+ bindService(intentBind, connection, BIND_AUTO_CREATE)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ unbindService(connection)
+ scannerServiceBound = false
+ }
+
+ private fun loadFragment(fragment: Fragment) {
+ mapFragment = null
+ scanFragment = null
+
+ when (fragment) {
+ is ScanFragment -> {
+ scanFragment = fragment
+ }
+
+ is MapFragment -> {
+ mapFragment = fragment
+ }
+ }
+
val transaction = supportFragmentManager.beginTransaction()
- transaction.replace(R.id.container,fragment)
+ transaction.replace(R.id.container, fragment)
transaction.commit()
}
+
+ private val connection = object : ServiceConnection {
+ override fun onServiceConnected(className: ComponentName, service: IBinder) {
+ val binder = service as ScannerService.LocalBinder
+ scannerService = binder.getService()
+ scannerServiceBound = true
+ binder.addListener(object : ScannerService.Callback {
+ @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
+ override fun onCalled(list: List<CellInfo?>) = handleUpdate(list)
+ })
+ }
+
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ scannerServiceBound = false
+ }
+ }
+
+ @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
+ fun handleUpdate(cells: List<CellInfo?>) {
+ val location = getLocation()
+
+ for (cellInfo in cells) {
+ if (cellInfo == null) {
+ continue
+ }
+
+ val cellLogItem = CellLogItem(cellInfo)
+ if (!cellLogItem.valid()) {
+ continue
+ }
+
+ val id = cellLogItem.id
+
+ if (currentCells[id] == null) {
+ val cellDbItem = cellDb.get(cellLogItem.lac, cellLogItem.cid)
+ if (cellDbItem != null) {
+ cellLogItem.description = cellDbItem.Description
+ cellLogItem.latitude = cellDbItem.Latitude
+ cellLogItem.longitude = cellDbItem.Longitude
+ if (location != null) {
+ cellLogItem.userLat = location.latitude
+ cellLogItem.userLon = location.longitude
+
+ }
+ }
+
+ currentCells[id] = cellLogItem
+ }
+
+ currentCells[id]?.update(cellInfo)
+ }
+
+ cellLogger.write(currentCells.values.toList())
+ scanFragment?.refresh(currentCells)
+ mapFragment?.refresh(currentCells)
+ }
+
+ fun startGetCellService() {
+ val service = Intent(baseContext, ScannerService::class.java)
+ scannerService.run = true
+ startService(service)
+ }
+
+ fun stopGetCellService() {
+ scannerService.run = false
+ }
+
+ @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
+ fun getLocation(): Location? {
+ val locationManager =
+ applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager
+ return locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/MainFragment.kt b/app/src/main/java/net/rctt/netmon/MainFragment.kt
deleted file mode 100644
index 4a6e01b..0000000
--- a/app/src/main/java/net/rctt/netmon/MainFragment.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-package net.rctt.netmon
-
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [MainFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
-class MainFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_main, container, false)
- }
-
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment MainFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- MainFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
- }
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/MapFragment.kt b/app/src/main/java/net/rctt/netmon/MapFragment.kt
index 0ec3737..d5f6cd0 100644
--- a/app/src/main/java/net/rctt/netmon/MapFragment.kt
+++ b/app/src/main/java/net/rctt/netmon/MapFragment.kt
@@ -5,55 +5,54 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.MapView
+import org.osmdroid.views.overlay.Marker
+import org.osmdroid.views.overlay.Overlay
+import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider
+import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
+import kotlin.collections.component1
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
-
-/**
- * A simple [Fragment] subclass.
- * Use the [MapFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
class MapFragment : Fragment() {
- // TODO: Rename and change types of parameters
- private var param1: String? = null
- private var param2: String? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- arguments?.let {
- param1 = it.getString(ARG_PARAM1)
- param2 = it.getString(ARG_PARAM2)
- }
- }
+ lateinit var map: MapView
+ var markersIds: HashMap<String, Overlay> = hashMapOf()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- // Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_map, container, false)
+ val view = inflater.inflate(R.layout.fragment_map, container, false)
+
+ map = view.findViewById(R.id.map)
+ map.setTileSource(TileSourceFactory.MAPNIK)
+ map.setMultiTouchControls(true)
+
+ val locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(activity), map)
+ locationOverlay.enableMyLocation()
+ locationOverlay.enableFollowLocation()
+ map.overlays.add(locationOverlay)
+ map.setZoomLevel(15.0)
+ return view
}
- companion object {
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment MapFragment.
- */
- // TODO: Rename and change types and number of parameters
- @JvmStatic
- fun newInstance(param1: String, param2: String) =
- MapFragment().apply {
- arguments = Bundle().apply {
- putString(ARG_PARAM1, param1)
- putString(ARG_PARAM2, param2)
- }
+ fun refresh(cells: HashMap<String, CellLogItem>) {
+ cells.forEach { (id, cell) ->
+ val marker = Marker(map)
+ marker.id = id
+ marker.setPosition(GeoPoint(cell.latitude, cell.longitude))
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ marker.setDefaultIcon()
+ marker.title = cell.description
+ map.overlays.add(marker)
+ markersIds[id] = marker
+ }
+
+
+ markersIds.forEach { (id, overlay) ->
+ if (!cells.containsKey(id)) {
+ map.overlays.remove(overlay)
}
+ }
}
} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/ScanFragment.kt b/app/src/main/java/net/rctt/netmon/ScanFragment.kt
new file mode 100644
index 0000000..16fc165
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/ScanFragment.kt
@@ -0,0 +1,63 @@
+package net.rctt.netmon
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.LinearLayout
+import androidx.fragment.app.Fragment
+
+class ScanFragment : Fragment() {
+ lateinit var cellsListView: LinearLayout
+ lateinit var startButton: Button
+ lateinit var stopButton: Button
+ var cellsListMap: HashMap<String, CellView> = hashMapOf()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val view = inflater.inflate(R.layout.fragment_scan, container, false)
+ cellsListView = view.findViewById(R.id.cells_list)
+ startButton = view.findViewById(R.id.start_button)
+ stopButton = view.findViewById(R.id.stop_button)
+
+ val mainActivity = (activity as MainActivity)
+
+ startButton.setOnClickListener {
+ mainActivity.startGetCellService()
+ }
+ stopButton.setOnClickListener {
+ mainActivity.stopGetCellService()
+ }
+
+ refresh(mainActivity.currentCells)
+ return view
+ }
+
+ fun refresh(cells: HashMap<String, CellLogItem>) {
+ cells.forEach { (id, cell) ->
+ var cellView = cellsListMap[id]
+ if (cellView == null) {
+ cellView = CellView(requireContext(), cell)
+ cellsListMap[id] = cellView
+ cellsListView.addView(cellView)
+ } else {
+ cellsListMap[id]!!.refresh(cell)
+ }
+ }
+
+ val toRemove: MutableList<String> = mutableListOf()
+ cellsListMap.forEach { (id) ->
+ if (!cells.containsKey(id)) {
+ toRemove.add(id)
+ }
+ }
+
+ toRemove.forEach { id ->
+ cellsListView.removeView(cellsListMap[id])
+ cellsListMap.remove(id)
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/net/rctt/netmon/ScannerService.kt b/app/src/main/java/net/rctt/netmon/ScannerService.kt
new file mode 100644
index 0000000..77a43bf
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/ScannerService.kt
@@ -0,0 +1,55 @@
+package net.rctt.netmon
+
+import android.Manifest
+import android.app.IntentService
+import android.content.Intent
+import android.os.Binder
+import android.os.Handler
+import android.os.IBinder
+import android.telephony.CellInfo
+import android.telephony.TelephonyManager
+import android.util.Log
+import androidx.annotation.RequiresPermission
+
+class ScannerService : IntentService(ScannerService::class.simpleName) {
+ interface Callback {
+ fun onCalled(list: List<CellInfo?>)
+ }
+
+ inner class LocalBinder : Binder() {
+ var callback: Callback? = null
+ fun getService(): ScannerService = this@ScannerService
+ fun addListener(listener: Callback?) {
+ callback = listener
+ }
+ }
+
+ val binder = LocalBinder()
+ val handler: Handler = Handler()
+ var run: Boolean = true
+
+ override fun onBind(intent: Intent): IBinder {
+ return binder
+ }
+
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ override fun onHandleIntent(p0: Intent?) {
+ val tel = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
+
+ while (run) {
+ handler.post { refresh(tel) }
+ Thread.sleep(1000)
+ }
+ Log.d("GetCellsService", "Stopping scan service")
+ }
+
+ @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
+ fun refresh(tel: TelephonyManager) {
+ Log.d("GetCellsService", "Refreshing cells list")
+ tel.requestCellInfoUpdate(mainExecutor, object : TelephonyManager.CellInfoCallback() {
+ override fun onCellInfo(cellList: List<CellInfo?>) {
+ binder.callback?.onCalled(cellList)
+ }
+ })
+ }
+}