summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbt <bt@rctt.net>2026-06-05 17:50:26 +0200
committerbt <bt@rctt.net>2026-06-07 21:41:04 +0200
commit39dc7341c8f19ead0912e036fbf730b6db06b147 (patch)
tree1d160fcf222ad640c45b2db905f392633936a460
parentcea9b1101a7197be413060a1b4cdd34a81b67d2f (diff)
downloadnetmon-39dc7341c8f19ead0912e036fbf730b6db06b147.tar.gz
netmon-39dc7341c8f19ead0912e036fbf730b6db06b147.zip
Scanner UI
-rw-r--r--app/build.gradle.kts2
-rw-r--r--app/src/main/AndroidManifest.xml8
-rw-r--r--app/src/main/java/net/rctt/netmon/Cell.kt104
-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/CellView.kt91
-rw-r--r--app/src/main/java/net/rctt/netmon/MainActivity.kt111
-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
-rw-r--r--app/src/main/res/layout/activity_main.xml6
-rw-r--r--app/src/main/res/layout/cell_view.xml17
-rw-r--r--app/src/main/res/layout/fragment_main.xml14
-rw-r--r--app/src/main/res/layout/fragment_map.xml9
-rw-r--r--app/src/main/res/layout/fragment_scan.xml50
-rw-r--r--app/src/main/res/menu/nav_menu.xml2
-rw-r--r--gradle/libs.versions.toml4
18 files changed, 518 insertions, 238 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ad8390f..9a7c0fe 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -47,6 +47,8 @@ dependencies {
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.cardview)
+ implementation(libs.androidx.databinding.common)
+ implementation(libs.androidx.databinding.runtime)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 05bd126..6541e4c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,23 +9,27 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
- android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
+ android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Base.Theme.Netmon">
+ <service
+ android:name=".ScannerService"
+ android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
+
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
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..f292109
--- /dev/null
+++ b/app/src/main/java/net/rctt/netmon/Cell.kt
@@ -0,0 +1,104 @@
+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()
+
+ 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)
+ }
+
+ fun valid(): Boolean {
+ return lac != CellInfo.UNAVAILABLE
+ }
+} \ 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/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 5a25086..6556b6d 100644
--- a/app/src/main/java/net/rctt/netmon/MainActivity.kt
+++ b/app/src/main/java/net/rctt/netmon/MainActivity.kt
@@ -1,32 +1,131 @@
package net.rctt.netmon
+import android.content.ComponentName
+import android.content.Intent
+import android.content.ServiceConnection
import android.os.Bundle
-import androidx.appcompat.app/**/.AppCompatActivity
+import android.os.IBinder
+import android.telephony.CellInfo
+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 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.main -> loadFragment(ScanFragment())
R.id.log -> loadFragment(LogFragment())
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 {
+ override fun onCalled(list: List<CellInfo?>) = handleUpdate(list)
+ })
+ }
+
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ scannerServiceBound = false
+ }
+ }
+
+ fun handleUpdate(cells: List<CellInfo?>) {
+ 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
+ }
+
+ currentCells[id] = cellLogItem
+ }
+
+ currentCells[id]?.update(cellInfo)
+ }
+
+ 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
+ }
} \ 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..0ba4a6b 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()
+ this.map.overlays.add(locationOverlay)
+
+ 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)
+ }
+ })
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index c78970c..221d2e9 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -2,6 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
@@ -11,10 +12,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_above="@+id/bottomNav" >
-
- </FrameLayout>
-
+ android:layout_above="@+id/bottomNav" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
diff --git a/app/src/main/res/layout/cell_view.xml b/app/src/main/res/layout/cell_view.xml
index 916f8cc..08b653d 100644
--- a/app/src/main/res/layout/cell_view.xml
+++ b/app/src/main/res/layout/cell_view.xml
@@ -35,7 +35,12 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Id" />
+ android:text="LAC" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="CID" />
<TextView
android:layout_width="wrap_content"
@@ -56,10 +61,16 @@
android:text="type" />
<TextView
- android:id="@+id/id"
+ android:id="@+id/lac"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="lac" />
+
+ <TextView
+ android:id="@+id/cid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="id" />
+ android:text="cid" />
<TextView
android:id="@+id/power"
diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml
deleted file mode 100644
index 366cbbe..0000000
--- a/app/src/main/res/layout/fragment_main.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainFragment">
-
- <!-- TODO: Update blank fragment layout -->
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/hello_blank_fragment" />
-
-</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
index 3ebc6c4..4639ec3 100644
--- a/app/src/main/res/layout/fragment_map.xml
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -5,10 +5,9 @@
android:layout_height="match_parent"
tools:context=".MapFragment">
- <!-- TODO: Update blank fragment layout -->
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="MAP" />
+ <org.osmdroid.views.MapView
+ android:id="@+id/map"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" />
</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_scan.xml b/app/src/main/res/layout/fragment_scan.xml
new file mode 100644
index 0000000..a18ef59
--- /dev/null
+++ b/app/src/main/res/layout/fragment_scan.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginHorizontal="5dp"
+ tools:context=".ScanFragment">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight=".70">
+
+ <LinearLayout
+ android:id="@+id/cells_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ </LinearLayout>
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginBottom="20dp"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/start_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:text="Start" />
+
+ <Button
+ android:id="@+id/stop_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Stop" />
+ </LinearLayout>
+ </LinearLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/nav_menu.xml b/app/src/main/res/menu/nav_menu.xml
index 5c42815..a4332ae 100644
--- a/app/src/main/res/menu/nav_menu.xml
+++ b/app/src/main/res/menu/nav_menu.xml
@@ -3,7 +3,7 @@
<item
android:id="@+id/main"
android:icon="@drawable/rounded_bar_chart_24"
- android:title="Main" />
+ android:title="Scan" />
<item
android:id="@+id/log"
android:icon="@drawable/outline_database_24"
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 20de420..f865af8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,6 +11,8 @@ constraintlayout = "2.2.1"
lifecycleLivedataKtx = "2.10.0"
lifecycleViewmodelKtx = "2.10.0"
cardview = "1.0.0"
+databindingCommon = "9.2.1"
+databindingRuntime = "9.2.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,6 +26,8 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" }
+androidx-databinding-common = { group = "androidx.databinding", name = "databinding-common", version.ref = "databindingCommon" }
+androidx-databinding-runtime = { group = "androidx.databinding", name = "databinding-runtime", version.ref = "databindingRuntime" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }