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/MainActivity.kt262
-rw-r--r--app/src/main/java/net/rctt/netmon/MapFragment.kt58
-rw-r--r--app/src/main/java/net/rctt/netmon/ScanFragment.kt63
-rw-r--r--app/src/main/java/net/rctt/netmon/ScannerService.kt55
9 files changed, 498 insertions, 258 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/MainActivity.kt b/app/src/main/java/net/rctt/netmon/MainActivity.kt
index 3aac48d..6cfd838 100644
--- a/app/src/main/java/net/rctt/netmon/MainActivity.kt
+++ b/app/src/main/java/net/rctt/netmon/MainActivity.kt
@@ -1,199 +1,153 @@
package net.rctt.netmon
import android.Manifest
-import android.annotation.SuppressLint
+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 android.os.Handler
-import android.os.Looper
-import android.telephony.CellIdentityNr
+import android.os.IBinder
import android.telephony.CellInfo
-import android.telephony.CellInfoGsm
-import android.telephony.CellInfoLte
-import android.telephony.CellInfoNr
-import android.telephony.CellInfoTdscdma
-import android.telephony.CellInfoWcdma
-import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.CellInfoCallback
-import android.widget.LinearLayout
-import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.view.ViewCompat
-import androidx.core.view.WindowInsetsCompat
-import com.jsoizo.kotlincsv.CsvDialect
-import com.jsoizo.kotlincsv.csvReader
-import com.jsoizo.kotlincsv.reader.readFromFile
+import androidx.fragment.app.Fragment
+import com.google.android.material.bottomnavigation.BottomNavigationView
import org.osmdroid.config.Configuration
-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.mylocation.GpsMyLocationProvider
-import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
-import java.io.File
+
class MainActivity : AppCompatActivity() {
- lateinit var cellsListView: LinearLayout
- lateinit var tel: TelephonyManager
- lateinit var cellsList: HashMap<Number, CellView>
- val cellDb = HashMap<Int, HashMap<Long, CellDbItem>>()
- lateinit var map: MapView
+ val cellDb = CellDb()
+ val cellLogger = CellLogger()
+ val currentCells: HashMap<String, CellLogItem> = hashMapOf()
+
+ 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)
- enableEdgeToEdge()
- setContentView(R.layout.activity_main)
- ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
- val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
- v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
- insets
- }
Configuration.getInstance().userAgentValue =
"Netmon/0.1 (+https://rctt.net; contact: bt@rctt.net)"
- map = findViewById(R.id.map)
- map.setTileSource(TileSourceFactory.MAPNIK)
- map.setMultiTouchControls(true)
- map.controller.setZoom(15)
- map.controller.setCenter(GeoPoint(51.10, 17.04));
+ setContentView(R.layout.activity_main)
- val locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(this), map)
- locationOverlay.enableMyLocation()
- this.map.overlays.add(locationOverlay)
+ loadFragment(ScanFragment())
+ bottomNav = findViewById(R.id.bottomNav)!!
- loadDb()
+ bottomNav.setOnItemSelectedListener {
+ when (it.itemId) {
+ R.id.main -> loadFragment(ScanFragment())
+ R.id.map -> loadFragment(MapFragment())
+ }
+ true
+ }
+ }
- cellsList = HashMap()
- cellsListView = findViewById(R.id.cellsList)
+ override fun onStart() {
+ super.onStart()
+ val intentBind = Intent(this, ScannerService::class.java)
+ bindService(intentBind, connection, BIND_AUTO_CREATE)
+ }
- tel = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
+ override fun onStop() {
+ super.onStop()
+ unbindService(connection)
+ scannerServiceBound = false
+ }
- val mainHandler = Handler(Looper.getMainLooper())
+ private fun loadFragment(fragment: Fragment) {
+ mapFragment = null
+ scanFragment = null
- mainHandler.post(object : Runnable {
- @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
- override fun run() {
- refresh()
- mainHandler.postDelayed(this, 1000)
+ when (fragment) {
+ is ScanFragment -> {
+ scanFragment = fragment
}
- })
- }
- fun loadDb() {
- val reader = csvReader {
- dialect = CsvDialect(delimiter = ';')
+ is MapFragment -> {
+ mapFragment = fragment
+ }
}
- 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)
+ val transaction = supportFragmentManager.beginTransaction()
+ transaction.replace(R.id.container, fragment)
+ transaction.commit()
+ }
- var lac = cellDb[item.LAC]
- if (lac == null) {
- cellDb[item.LAC] = HashMap()
- lac = cellDb[item.LAC]
- }
+ 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)
+ })
+ }
- lac?.set(item.CID, item)
- }
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ scannerServiceBound = false
}
}
- fun findInDb(lac: Int, cid: Long): CellDbItem? {
- return cellDb[lac]?.get(cid)
- }
+ @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
+ fun handleUpdate(cells: List<CellInfo?>) {
+ val location = getLocation()
- @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
- fun refresh() {
- tel.requestCellInfoUpdate(mainExecutor, object : CellInfoCallback() {
- @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
- override fun onCellInfo(cellList: List<CellInfo?>) {
- cellsListView.removeAllViews()
- for (cell in cellList) {
- if (cell == null) {
- continue
- }
- addCellView(cell)
- }
+ for (cellInfo in cells) {
+ if (cellInfo == null) {
+ continue
}
- })
- }
- @SuppressLint("SetTextI18n")
- fun addCellView(cell: CellInfo) {
- val id = getCellId(cell)
- if (id == 65535 || id == 2147483647) {
- return
- }
+ val cellLogItem = CellLogItem(cellInfo)
+ if (!cellLogItem.valid()) {
+ continue
+ }
- val lac = getCellLac(cell)
- val dbItem = findInDb(lac.toInt(), id.toLong())
- val desc = dbItem?.Description ?: ""
-
- var cellView = cellsList[id]
- if (cellView == null) {
- cellView = CellView(this, id)
- cellsList[id] = cellView
- cellView.setMarker(Marker(map))
- } else {
- cellsListView.removeView(cellView)
- map.overlays.remove(cellView.mapMarker)
- }
+ 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
- when (cell) {
- is CellInfoGsm -> cellView.set(cell)
- is CellInfoLte -> cellView.set(cell)
- is CellInfoNr -> cellView.set(cell)
- is CellInfoTdscdma -> cellView.set(cell)
- is CellInfoWcdma -> cellView.set(cell)
+ }
+ }
+
+ currentCells[id] = cellLogItem
+ }
+
+ currentCells[id]?.update(cellInfo)
}
- cellView.refresh()
- cellView.setDesc(desc)
- cellView.setLocation(dbItem?.Latitude ?: 0.0, dbItem?.Longitude ?: 0.0)
- cellsListView.addView(cellView)
- map.overlays.add(cellView.mapMarker)
- }
-}
-
-fun getCellId(cell: CellInfo): Number {
- when (cell) {
- is CellInfoGsm -> return cell.cellIdentity.cid
- is CellInfoLte -> return cell.cellIdentity.ci
- is CellInfoNr -> return (cell.cellIdentity as CellIdentityNr).nci
- is CellInfoTdscdma -> return cell.cellIdentity.cid
- is CellInfoWcdma -> return cell.cellIdentity.cid
+ cellLogger.write(currentCells.values.toList())
+ scanFragment?.refresh(currentCells)
+ mapFragment?.refresh(currentCells)
}
- return 0
-}
+ fun startGetCellService() {
+ val service = Intent(baseContext, ScannerService::class.java)
+ scannerService.run = true
+ startService(service)
+ }
-fun getCellLac(cell: CellInfo): Number {
- when (cell) {
- is CellInfoGsm -> return cell.cellIdentity.lac
- is CellInfoLte -> return cell.cellIdentity.tac
- is CellInfoNr -> return (cell.cellIdentity as CellIdentityNr).tac
- is CellInfoTdscdma -> return cell.cellIdentity.lac
- is CellInfoWcdma -> return cell.cellIdentity.lac
+ fun stopGetCellService() {
+ scannerService.run = false
}
- return 0
+ @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/MapFragment.kt b/app/src/main/java/net/rctt/netmon/MapFragment.kt
new file mode 100644
index 0000000..d5f6cd0
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/MapFragment.kt
@@ -0,0 +1,58 @@
+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
+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
+
+class MapFragment : Fragment() {
+ lateinit var map: MapView
+ var markersIds: HashMap<String, Overlay> = hashMapOf()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ 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
+ }
+
+ 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)
+ }
+ })
+ }
+}