Skip to content

Commit b04f94e

Browse files
committed
add location service
1 parent 4839eee commit b04f94e

File tree

10 files changed

+226
-60
lines changed

10 files changed

+226
-60
lines changed

app/src/main/java/com/lcl/lclmeasurementtool/MainActivity2.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,10 @@ class MainActivity2 : ComponentActivity() {
140140
// including IME animations
141141
WindowCompat.setDecorFitsSystemWindows(window, false)
142142
setContent {
143-
if (uiState == MainActivityUiState.Login) {
144-
Login(viewModel = viewModel)
145-
return@setContent
146-
}
143+
// if (uiState == MainActivityUiState.Login) {
144+
// Login(viewModel = viewModel)
145+
// return@setContent
146+
// }
147147
LCLApp(windowSizeClass = calculateWindowSizeClass(activity = this), networkMonitor, simStateMonitor)
148148
}
149149
}

app/src/main/java/com/lcl/lclmeasurementtool/MainActivityViewModel.kt

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import com.lcl.lclmeasurementtool.Utils.ECDSA
1414
import com.lcl.lclmeasurementtool.Utils.Hex
1515
import com.lcl.lclmeasurementtool.Utils.SecurityUtils
1616
import com.lcl.lclmeasurementtool.constants.NetworkConstants
17+
import com.lcl.lclmeasurementtool.datasource.LocationDataSource
1718
import com.lcl.lclmeasurementtool.features.iperf.IperfRunner
1819
import com.lcl.lclmeasurementtool.features.iperf.IperfStatus
1920
import com.lcl.lclmeasurementtool.features.ping.Ping
2021
import com.lcl.lclmeasurementtool.features.ping.PingError
2122
import com.lcl.lclmeasurementtool.features.ping.PingErrorCase
2223
import com.lcl.lclmeasurementtool.features.ping.PingResult
24+
import com.lcl.lclmeasurementtool.location.LocationService
2325
import com.lcl.lclmeasurementtool.model.datamodel.QRCodeKeysModel
2426
import com.lcl.lclmeasurementtool.model.datamodel.RegistrationModel
2527
import com.lcl.lclmeasurementtool.model.datamodel.UserData
@@ -37,7 +39,8 @@ import javax.inject.Inject
3739
@HiltViewModel
3840
class MainActivityViewModel @Inject constructor(
3941
private val userDataRepository: UserDataRepository,
40-
private val networkApi: NetworkApiRepository
42+
private val networkApi: NetworkApiRepository,
43+
private val locationService: LocationService
4144
) : ViewModel() {
4245

4346
companion object {
@@ -53,6 +56,12 @@ class MainActivityViewModel @Inject constructor(
5356
started = SharingStarted.WhileSubscribed(5_000)
5457
)
5558

59+
fun getLocation() = viewModelScope.launch {
60+
locationService.lastLocation().collect {
61+
Log.d(TAG, it.toString())
62+
}
63+
}
64+
5665
// Authentication
5766
fun login(hPKR: ByteString, skT: ByteString) = viewModelScope.launch {
5867
userDataRepository.setKeys(hPKR, skT)
@@ -189,10 +198,16 @@ class MainActivityViewModel @Inject constructor(
189198

190199

191200
// Network Testing
192-
var uploadResult: ConnectivityTestResult.Result? = null
193-
var downloadResult: ConnectivityTestResult.Result? = null
194201
private var _pingResult = MutableStateFlow(PingResultState())
202+
private var _downloadResult = MutableStateFlow(ConnectivityTestResult())
203+
private var _uploadResult = MutableStateFlow(ConnectivityTestResult())
204+
205+
195206
val pingResult: StateFlow<PingResultState> = _pingResult.asStateFlow()
207+
var downloadResult: StateFlow<ConnectivityTestResult> = _downloadResult.asStateFlow()
208+
var uploadResult: StateFlow<ConnectivityTestResult> = _uploadResult.asStateFlow()
209+
210+
196211
private val _isTestActive = MutableStateFlow(false)
197212
val isTestActive = _isTestActive.asStateFlow()
198213

@@ -205,8 +220,11 @@ class MainActivityViewModel @Inject constructor(
205220
}
206221
.onCompletion {
207222
Log.d(TAG, "isActive = false")
208-
_pingResult.value = PingResultState.Error(PingError(PingErrorCase.CANCELLED, it?.message))
209223
_isTestActive.value = false
224+
if (it != null) {
225+
// save to DB and send over the network
226+
Log.d(TAG, "Error is ${it.message}")
227+
}
210228
}
211229
.collect {
212230
ensureActive()
@@ -223,42 +241,46 @@ class MainActivityViewModel @Inject constructor(
223241
}
224242

225243
fun getUploadResult(context: Context) = viewModelScope.launch {
226-
IperfRunner().getTestResult(IperfRunner.iperfUploadConfig, context.cacheDir).collect { result ->
227-
uploadResult = when(result.status) {
228-
IperfStatus.RUNNING -> {
229-
ConnectivityTestResult.Result(result.bandWidth, Color.LightGray)
230-
}
231-
IperfStatus.FINISHED -> {
232-
ConnectivityTestResult.Result(result.bandWidth, Color.Black)
233-
}
234-
IperfStatus.ERROR -> TODO()
244+
IperfRunner().getTestResult(IperfRunner.iperfUploadConfig, context.cacheDir)
245+
.collect { result ->
246+
ensureActive()
247+
248+
_uploadResult.value = when(result.status) {
249+
IperfStatus.RUNNING -> ConnectivityTestResult.Result(result.bandWidth, Color.LightGray)
250+
IperfStatus.FINISHED -> ConnectivityTestResult.Result(result.bandWidth, Color.Black)
251+
IperfStatus.ERROR -> ConnectivityTestResult.Error(result.errorMSg!!)
235252
}
236253
}
237254
}
238255

239256

240257
fun getDownloadResult(context: Context) = viewModelScope.launch {
241-
IperfRunner().getTestResult(IperfRunner.iperfDownloadConfig, context.cacheDir).collect { result ->
242-
downloadResult = when(result.status) {
243-
IperfStatus.RUNNING -> {
244-
ConnectivityTestResult.Result(result.bandWidth, Color.LightGray)
245-
}
246-
IperfStatus.FINISHED -> {
247-
ConnectivityTestResult.Result(result.bandWidth, Color.Black)
248-
}
249-
IperfStatus.ERROR -> TODO()
258+
IperfRunner().getTestResult(IperfRunner.iperfDownloadConfig, context.cacheDir)
259+
.onCompletion {
260+
_isTestActive.value = false
261+
// TODO: write data to the database
262+
}
263+
.collect { result ->
264+
ensureActive()
265+
266+
_downloadResult.value = when(result.status) {
267+
IperfStatus.RUNNING -> ConnectivityTestResult.Result(result.bandWidth, Color.LightGray)
268+
IperfStatus.FINISHED -> ConnectivityTestResult.Result(result.bandWidth, Color.Black)
269+
IperfStatus.ERROR -> ConnectivityTestResult.Error(result.errorMSg!!)
250270
}
251271
}
252272
}
273+
253274
}
254275

255276
sealed interface MainActivityUiState {
256277
object Login : MainActivityUiState
257278
data class Success(val userData: UserData) : MainActivityUiState
258279
}
259280

260-
sealed interface ConnectivityTestResult {
261-
data class Result (val result: String, val color: Color): ConnectivityTestResult
281+
open class ConnectivityTestResult {
282+
data class Result (val result: String, val color: Color): ConnectivityTestResult()
283+
data class Error(val error: String): ConnectivityTestResult()
262284
}
263285

264286
open class PingResultState {

app/src/main/java/com/lcl/lclmeasurementtool/datasource/LCLNetworkDataSource.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.lcl.lclmeasurementtool.datasource
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import android.location.Location
6+
import com.google.android.gms.location.*
7+
import com.google.android.gms.tasks.CancellationTokenSource
8+
import com.google.android.gms.tasks.OnCompleteListener
9+
import com.google.android.gms.tasks.OnSuccessListener
10+
import com.hjq.permissions.Permission
11+
import com.hjq.permissions.XXPermissions
12+
import com.lcl.lclmeasurementtool.location.LocationService
13+
import dagger.hilt.android.qualifiers.ApplicationContext
14+
import kotlinx.coroutines.channels.awaitClose
15+
import kotlinx.coroutines.flow.*
16+
import javax.inject.Inject
17+
18+
class LocationDataSource @Inject constructor(
19+
@ApplicationContext private val context: Context
20+
): LocationService {
21+
companion object {
22+
const val LOCATION_INTERVAL = 10000L
23+
const val TAG = "LocationDataSource"
24+
}
25+
26+
private val fusedLocationProviderClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
27+
private val currentLocationRequest = CurrentLocationRequest.Builder().setPriority(Priority.PRIORITY_BALANCED_POWER_ACCURACY).setDurationMillis(LOCATION_INTERVAL).build()
28+
29+
30+
@SuppressLint("MissingPermission")
31+
override fun lastLocation() = callbackFlow {
32+
if (!XXPermissions.isGranted(context, Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION)) {
33+
XXPermissions
34+
.with(context)
35+
.permission(Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION)
36+
.request { permissions, allGranted ->
37+
if (!allGranted) {
38+
TODO("Show message to the user")
39+
}
40+
}
41+
}
42+
43+
val callback = OnSuccessListener<Location> { task -> trySend(task) }
44+
45+
fusedLocationProviderClient
46+
.lastLocation
47+
.addOnSuccessListener(callback)
48+
49+
awaitClose {
50+
51+
}
52+
}
53+
54+
@SuppressLint("MissingPermission")
55+
fun getCurrentLocation() = callbackFlow<Location> {
56+
if (!XXPermissions.isGranted(context, Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION)) {
57+
XXPermissions
58+
.with(context)
59+
.permission(Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION)
60+
.request { permissions, allGranted ->
61+
if (!allGranted) {
62+
TODO("Show message to the user")
63+
}
64+
}
65+
}
66+
val callback = OnSuccessListener<Location> { task -> trySend(task) }
67+
fusedLocationProviderClient.getCurrentLocation(currentLocationRequest, CancellationTokenSource().token).addOnSuccessListener(callback)
68+
}
69+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.lcl.lclmeasurementtool.datasource
2+
3+
import android.content.Context
4+
import android.os.Build
5+
import android.os.Build.VERSION_CODES.S
6+
import android.telephony.PhoneStateListener
7+
import android.telephony.SignalStrength
8+
import android.telephony.TelephonyCallback
9+
import android.telephony.TelephonyManager
10+
import androidx.annotation.RequiresApi
11+
import androidx.core.content.getSystemService
12+
import dagger.hilt.android.qualifiers.ApplicationContext
13+
import kotlinx.coroutines.FlowPreview
14+
import kotlinx.coroutines.channels.awaitClose
15+
import kotlinx.coroutines.flow.*
16+
import java.util.concurrent.Executor
17+
import java.util.concurrent.Executors
18+
import javax.inject.Inject
19+
20+
class SignalStrengthDataSource @Inject constructor(
21+
@ApplicationContext private val context: Context
22+
) {
23+
@OptIn(FlowPreview::class)
24+
val signalStrength = callbackFlow {
25+
val telephonyManager = context.getSystemService<TelephonyManager>()
26+
val executor = Executors.newSingleThreadExecutor()
27+
28+
if (Build.VERSION.SDK_INT >= 31) {
29+
val callback = object: TelephonyCallback(), TelephonyCallback.SignalStrengthsListener {
30+
override fun onSignalStrengthsChanged(s: SignalStrength) {
31+
channel.trySend(s)
32+
}
33+
}
34+
35+
telephonyManager?.registerTelephonyCallback(executor, callback)
36+
37+
awaitClose {
38+
telephonyManager?.unregisterTelephonyCallback(callback)
39+
executor.shutdown()
40+
}
41+
} else {
42+
@Suppress("OVERRIDE_DEPRECATION")
43+
val callback = object : PhoneStateListener(executor) {
44+
override fun onSignalStrengthsChanged(s: SignalStrength?) {
45+
super.onSignalStrengthsChanged(s)
46+
channel.trySend(s)
47+
}
48+
}
49+
50+
telephonyManager?.listen(callback, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)
51+
52+
awaitClose {
53+
telephonyManager?.listen(callback, 0)
54+
executor.shutdown()
55+
}
56+
}
57+
}.conflate().sample(15000L).distinctUntilChanged()
58+
}

app/src/main/java/com/lcl/lclmeasurementtool/features/iperf/IperfRunner.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,12 @@ class IperfRunner {
110110
doneSignal.countDown()
111111
}
112112

113-
114-
if (!isActive) {
115-
client.cancelTest()
116-
onStopped {
117-
client.cancelTest()
118-
}
119-
}
120-
121113
awaitClose {
122114
onStopped {
123115
client.cancelTest()
124116
}
125117
}
126-
}.flowOn(Dispatchers.IO).conflate().cancellable()
118+
}.conflate().flowOn(Dispatchers.IO).cancellable()
127119

128120
private fun onStopped(cancellation: () -> Unit) {
129121
cancellation()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.lcl.lclmeasurementtool.location
2+
3+
import android.location.Location
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface LocationService {
7+
// val currentLocation: Flow<Location>
8+
fun lastLocation(): Flow<Location>
9+
}

app/src/main/java/com/lcl/lclmeasurementtool/modules/DataModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.lcl.lclmeasurementtool.modules
22

33
import com.lcl.lclmeasurementtool.datasource.ConnectivityMonitorDataSource
4+
import com.lcl.lclmeasurementtool.datasource.LocationDataSource
45
import com.lcl.lclmeasurementtool.datasource.SimStateMonitorDataSource
6+
import com.lcl.lclmeasurementtool.location.LocationService
57
import com.lcl.lclmeasurementtool.model.datamodel.ConnectivityReportModel
68
import com.lcl.lclmeasurementtool.model.datamodel.SignalStrengthReportModel
79
import com.lcl.lclmeasurementtool.model.repository.*
@@ -30,6 +32,11 @@ interface DataModule {
3032
simStateMonitor: SimStateMonitorDataSource
3133
): SimStateMonitor
3234

35+
@Binds
36+
fun bindsLocationService(
37+
locationService: LocationDataSource
38+
): LocationService
39+
3340
@Binds
3441
fun bindsNetworkAPI(
3542
lclApiRepository: LCLApiRepository

0 commit comments

Comments
 (0)