diff options
Diffstat (limited to 'app/src/main/java')
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/Cell.kt | 122 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/CellDb.kt | 51 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/CellDbItem.kt | 31 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/CellLogger.kt | 23 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/CellView.kt | 91 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/LogFragment.kt | 59 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/MainActivity.kt | 135 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/MainFragment.kt | 59 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/MapFragment.kt | 79 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/ScanFragment.kt | 63 | ||||
| -rw-r--r-- | app/src/main/java/net/rctt/netmon/ScannerService.kt | 55 |
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) + } + }) + } +} |
