Skip to content

Commit

Permalink
Creating a user mediated permission for starting and stopping the app…
Browse files Browse the repository at this point in the history
…lication.
  • Loading branch information
itissid committed Nov 20, 2024
1 parent 8c946c8 commit da843a3
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 59 deletions.
9 changes: 9 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,18 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2'
implementation 'com.google.android.gms:play-services-location:21.3.0'

def work_version = "2.9.1"

// (Java only)
implementation "androidx.work:work-runtime:$work_version"

// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
debugImplementation 'androidx.compose.ui:ui-tooling:1.7.2'


}
15 changes: 12 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
<!-- Declare the Foreground Service -->
<service
android:name=".service.PrivyForegroundService"
android:exported="false"
android:foregroundServiceType="location"
/>
android:exported="true"
android:foregroundServiceType="location">
<intent-filter>
<action android:name="me.itissid.privyloci.service.PrivyForegroundService" />
</intent-filter>
</service>

<activity
android:name="me.itissid.privyloci.MainActivity"
android:exported="true"
Expand All @@ -33,7 +37,9 @@
android:name="android.app.lib_name"
android:value="" />
</activity>
<receiver android:name=".service.NotificationDismissedReceiver" />
</application>

<!-- Add Permissions -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Expand All @@ -42,4 +48,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="me.itissid.privyloci.service.PrivyForegroundService" />


</manifest>
54 changes: 27 additions & 27 deletions app/src/main/java/me/itissid/privyloci/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ package me.itissid.privyloci

import android.Manifest
import android.app.Activity
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.telephony.ServiceState
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
Expand All @@ -32,36 +33,33 @@ import androidx.navigation.compose.composable

import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Place
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.material3.FloatingActionButton
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import androidx.navigation.compose.NavHost
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted

import me.itissid.privyloci.data.DataProvider

import me.itissid.privyloci.datamodels.AppContainer
import me.itissid.privyloci.datamodels.PlaceTag
import me.itissid.privyloci.datamodels.Subscription
import me.itissid.privyloci.datamodels.SubscriptionType
import me.itissid.privyloci.ui.HomeScreen
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.permissions.rememberPermissionState

import me.itissid.privyloci.ui.PlacesAndAssetsScreen
import me.itissid.privyloci.ui.theme.PrivyLociTheme
import dagger.hilt.android.AndroidEntryPoint
import me.itissid.privyloci.data.DataProvider.processAppContainers
import me.itissid.privyloci.service.PrivyForegroundService
import me.itissid.privyloci.service.ServiceStateHolder
import me.itissid.privyloci.service.startPrivyForegroundService
import me.itissid.privyloci.service.stopPrivyForegroundService
import me.itissid.privyloci.ui.AdaptiveIcon
import me.itissid.privyloci.ui.LocationPermissionRationaleDialogue
import me.itissid.privyloci.ui.PermissionDeniedScreen
import me.itissid.privyloci.util.Logger

// TODO(Sid): Replace with real data after demo.
data class MockData(
Expand Down Expand Up @@ -104,7 +102,10 @@ fun MainScreenWrapper() {
val context = LocalContext.current as Activity

var rationaleState by remember { mutableStateOf(false) }


// TODO: Encapsulate the permision code in its own class.
// TODO: Ask for background permissions if I don't take the foreground permissions route.
val foregroundLocationPermissionState = rememberMultiplePermissionsState(
permissions = listOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Expand Down Expand Up @@ -156,11 +157,12 @@ fun MainScreenWrapper() {
}
)
}
// TODO: Ask for background permissions if I don't take the foreground permissions route.
// TODO: Consider using a viewmodel to get the data from the daos.
val database = MainApplication.database

val subscriptionDao = database.subscriptionDao()
val placeTagDao = database.placeTagDao()
// Coro for the win!
val places by placeTagDao.getAllPlaceTags().collectAsState(initial = emptyList())
val subscriptions by subscriptionDao.getAllSubscriptions().collectAsState(initial = emptyList())
Log.d(TAG, "Places: ${places.size}")
Expand All @@ -175,23 +177,16 @@ fun MainScreenWrapper() {
foregroundLocationPermissionState.allPermissionsGranted,
onLocationIconClick
)
LaunchedEffect(foregroundLocationPermissionState.allPermissionsGranted) {
if (foregroundLocationPermissionState.allPermissionsGranted) {
startPrivyForegroundService(context)
} else {
stopPrivyForegroundService(context)
}
}
}

fun startPrivyForegroundService(context: Context) {
val serviceIntent = Intent(context, PrivyForegroundService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)
}

fun stopPrivyForegroundService(context: Context) {
val serviceIntent = Intent(context, PrivyForegroundService::class.java)
context.stopService(serviceIntent)
if (foregroundLocationPermissionState.allPermissionsGranted && !ServiceStateHolder.isServiceRunning) {
// TODO: On starting the service I want to show some of the data about how many subscriptions are active
// and being tracked.
startPrivyForegroundService(context)
} else if (!foregroundLocationPermissionState.allPermissionsGranted) {
// TODO: Warn the user after some time(probably like in a timer) that the service is not running
// because the permission is not granted.
stopPrivyForegroundService(context)
}
}


Expand All @@ -215,6 +210,11 @@ fun MainScreen(
onLocationIconClick = onLocationIconClick
)
},
floatingActionButton = {
FloatingActionButton(onClick = { navController.navigate("createEvent") }) {
Icon(Icons.Filled.Add, contentDescription = "Add Event")
}
},
bottomBar = { BottomNavBar(navController) }
) { innerPadding ->
NavHost(
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/java/me/itissid/privyloci/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ class MainApplication : Application() {
Logger.e(this::class.toString(), "Error populating database", e)
}
}


}

override fun onOpen(db: SupportSQLiteDatabase) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/me/itissid/privyloci/SensorManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import javax.inject.Singleton
@Singleton
class SensorManager @Inject constructor(
private val googleLocationSensor: GoogleFusedLocationSensor
// Other sensors
// Add Other sensors as we implement them
) {
// private val locationMutableFlow = MutableSharedFlow<Location>(replay = 20)
// val locationFlow: SharedFlow<Location> = locationMutableFlow.asSharedFlow()
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/java/me/itissid/privyloci/SubscriptionManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ class SubscriptionManager @Inject constructor(
suspend fun initialize(context: Context) {

// Load subscriptions from database
// TODO: Consider using a flow to update the activeSubscriptions itself, that way addition and deletion would not have to start and stop every processor when initialize is called again.
CoroutineScope(Dispatchers.IO).launch {
// val subscriptions = subscriptionDao.getAllSubscriptions()
// For testing, add a mock subscription
subscriptionDao.getAllSubscriptions().collect { subscriptions ->
// we call initialize.
activeSubscriptions.forEach { subscription ->
eventProcessors[subscription.subscriptionId]?.stopProcessing()
}
Expand All @@ -44,7 +46,7 @@ class SubscriptionManager @Inject constructor(
"Active subscriptions processed by SubscriptionManager: ${activeSubscriptions.size}"
)
}
subscriptions.forEach { subscription ->
activeSubscriptions.forEach { subscription ->
val processor = createEventProcessor(subscription, context)
processor.startProcessing()
eventProcessors[subscription.subscriptionId] = processor
Expand All @@ -55,10 +57,10 @@ class SubscriptionManager @Inject constructor(
"calling manageSensors for ${activeSubscriptions.size} subscriptions"
)
}
// N2S: Leaving the sensor manager start code here for now. Not sure if this is the right place for it.
manageSensors()
}
}
// N2S: Leaving the sensor manager start code here for now. Not sure if this is the right place for it.
}

private fun createEventProcessor(subscription: Subscription, context: Context): EventProcessor {
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/java/me/itissid/privyloci/datamodels/Dao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,4 @@ interface PlaceTagDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPlaceTags(placeTags: List<PlaceTag>)


}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class GeofenceEventProcessor(
}

private fun processLocation(location: Location) {
// TODO: Reverse the location check with debounce time and ENTER/DWELL/EXIT logic.
Log.d(
"GeofenceEventProcessor",
"Processing location ${
Expand All @@ -77,6 +78,7 @@ class GeofenceEventProcessor(
)
}"
)

val distance = FloatArray(1)
Location.distanceBetween(
location.latitude, location.longitude,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import me.itissid.privyloci.util.Logger
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -30,6 +31,7 @@ class GoogleFusedLocationSensor @Inject constructor(

@SuppressLint("MissingPermission")
override fun start() {
Logger.d(this::class.java.simpleName, "Is emitting: $isEmitting")
if (isEmitting) return
isEmitting = true
CoroutineScope(Dispatchers.Default).launch {
Expand All @@ -42,7 +44,11 @@ class GoogleFusedLocationSensor @Inject constructor(
}

override fun stop() {
fusionLocationProviderClient.removeLocationUpdates(locationCallback)
try {
fusionLocationProviderClient.removeLocationUpdates(locationCallback)
} finally {
isEmitting = false
}
}

private val locationRequest = LocationRequest.Builder(
Expand Down
Loading

0 comments on commit da843a3

Please sign in to comment.