diff options
Diffstat (limited to 'app/src')
24 files changed, 629 insertions, 329 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2c816e..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/Theme.Netmon"> + 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..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) + } + }) + } +} diff --git a/app/src/main/res/drawable/baseline_map_24.xml b/app/src/main/res/drawable/baseline_map_24.xml new file mode 100644 index 0000000..58f2947 --- /dev/null +++ b/app/src/main/res/drawable/baseline_map_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/> + +</vector> diff --git a/app/src/main/res/drawable/outline_database_24.xml b/app/src/main/res/drawable/outline_database_24.xml new file mode 100644 index 0000000..651d3b6 --- /dev/null +++ b/app/src/main/res/drawable/outline_database_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M480,840Q329,840 224.5,793.5Q120,747 120,680L120,280Q120,214 225.5,167Q331,120 480,120Q629,120 734.5,167Q840,214 840,280L840,680Q840,747 735.5,793.5Q631,840 480,840ZM480,361Q569,361 659,335.5Q749,310 760,281Q749,252 659.5,226Q570,200 480,200Q389,200 301.5,225.5Q214,251 200,281Q214,311 301.5,336Q389,361 480,361ZM480,560Q522,560 561,556Q600,552 635.5,544.5Q671,537 702.5,526Q734,515 760,501L760,381Q734,395 702.5,406Q671,417 635.5,424.5Q600,432 561,436Q522,440 480,440Q438,440 398,436Q358,432 322.5,424.5Q287,417 256,406Q225,395 200,381L200,501Q225,515 256,526Q287,537 322.5,544.5Q358,552 398,556Q438,560 480,560ZM480,760Q526,760 573.5,753Q621,746 661,734.5Q701,723 728,708.5Q755,694 760,679L760,581Q734,595 702.5,606Q671,617 635.5,624.5Q600,632 561,636Q522,640 480,640Q438,640 398,636Q358,632 322.5,624.5Q287,617 256,606Q225,595 200,581L200,680Q205,695 231.5,709Q258,723 298,734.5Q338,746 386,753Q434,760 480,760Z"/> + +</vector> diff --git a/app/src/main/res/drawable/rounded_bar_chart_24.xml b/app/src/main/res/drawable/rounded_bar_chart_24.xml new file mode 100644 index 0000000..146a07d --- /dev/null +++ b/app/src/main/res/drawable/rounded_bar_chart_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M680,800Q663,800 651.5,788.5Q640,777 640,760L640,560Q640,543 651.5,531.5Q663,520 680,520L760,520Q777,520 788.5,531.5Q800,543 800,560L800,760Q800,777 788.5,788.5Q777,800 760,800L680,800ZM440,800Q423,800 411.5,788.5Q400,777 400,760L400,200Q400,183 411.5,171.5Q423,160 440,160L520,160Q537,160 548.5,171.5Q560,183 560,200L560,760Q560,777 548.5,788.5Q537,800 520,800L440,800ZM200,800Q183,800 171.5,788.5Q160,777 160,760L160,400Q160,383 171.5,371.5Q183,360 200,360L280,360Q297,360 308.5,371.5Q320,383 320,400L320,760Q320,777 308.5,788.5Q297,800 280,800L200,800Z"/> + +</vector> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 76aa1e4..221d2e9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,42 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/main" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/test" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:context=".MainActivity"> - <LinearLayout - android:layout_width="0dp" + <FrameLayout + android:id="@+id/container" + android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" + android:layout_above="@+id/bottomNav" /> + + <com.google.android.material.bottomnavigation.BottomNavigationView + android:id="@+id/bottomNav" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:scrollIndicators="left" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> - - <TextView - android:id="@+id/haederCell" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Cell Info" - android:textSize="20sp" /> - - <ScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <LinearLayout - android:id="@+id/cellsList" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" /> - </ScrollView> - <org.osmdroid.views.MapView android:id="@+id/map" - android:layout_width="fill_parent" - android:layout_height="fill_parent" /> - </LinearLayout> - -</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file + app:menu="@menu/nav_menu" /> +</RelativeLayout>
\ No newline at end of file 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_map.xml b/app/src/main/res/layout/fragment_map.xml new file mode 100644 index 0000000..4639ec3 --- /dev/null +++ b/app/src/main/res/layout/fragment_map.xml @@ -0,0 +1,13 @@ +<?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=".MapFragment"> + + <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 new file mode 100644 index 0000000..574205b --- /dev/null +++ b/app/src/main/res/menu/nav_menu.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/main" + android:icon="@drawable/rounded_bar_chart_24" + android:title="Scan" /> + <item + android:id="@+id/map" + android:icon="@drawable/baseline_map_24" + android:title="Map" /> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 04e74eb..e5f8fdc 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -1,6 +1,2 @@ <resources> - <style name="Widget.Theme.Netmon.MyView" parent=""> - <item name="android:background">@color/gray_600</item> - <item name="exampleColor">@color/light_blue_600</item> - </style> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 9b38736..17a54dd 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,5 +1,2 @@ <resources xmlns:tools="http://schemas.android.com/tools"> - <style name="Base.Theme.Netmon" parent="Theme.Material3.DayNight.NoActionBar"> - <item name="android:windowBackground">#FF000000</item> - </style> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index e2337bb..55344e5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,9 +1,3 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <color name="black">#FF000000</color> - <color name="white">#FFFFFFFF</color> - <color name="light_blue_400">#FF29B6F6</color> - <color name="light_blue_600">#FF039BE5</color> - <color name="gray_400">#FFBDBDBD</color> - <color name="gray_600">#FF757575</color> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c29dfa..723086d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,4 +3,6 @@ <string name="title_activity_main2">MainActivity2</string> <string name="tab_text_1">Tab 1</string> <string name="tab_text_2">Tab 2</string> + <!-- TODO: Remove or change this placeholder text --> + <string name="hello_blank_fragment">Hello blank fragment</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5ed5dd0..e5f8fdc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,6 +1,2 @@ <resources> - <style name="Widget.Theme.Netmon.MyView" parent=""> - <item name="android:background">@color/gray_400</item> - <item name="exampleColor">@color/light_blue_400</item> - </style> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 8c1e2ef..d292522 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,18 +1,4 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> - <!-- Base application theme. --> - <style name="Base.Theme.Netmon" parent="Theme.Material3.DayNight.NoActionBar"> - <!-- Customize your light theme here. --> - <!-- <item name="colorPrimary">@color/my_light_primary</item> --> +<resources> + <style name="Base.Theme.Netmon" parent="Theme.Material3.DayNight"> </style> - - <style name="Theme.Netmon" parent="Base.Theme.Netmon" /> - - <style name="Theme.Netmon.NoActionBar"> - <item name="windowActionBar">false</item> - <item name="windowNoTitle">true</item> - </style> - - <style name="Theme.Netmon.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> - - <style name="Theme.Netmon.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
\ No newline at end of file |
