Skip to content

Instantly share code, notes, and snippets.

@lgawin
Last active January 6, 2024 00:46
Show Gist options
  • Save lgawin/71839117f261cdc36701c7e42bebbd8b to your computer and use it in GitHub Desktop.
Save lgawin/71839117f261cdc36701c7e42bebbd8b to your computer and use it in GitHub Desktop.
Helper for requesting roles with example usage
import android.app.role.RoleManager
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import java.util.UUID
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
sealed interface RoleAvailability {
data object NotAvailable : RoleAvailability
data object Available : RoleAvailability
data object Held : RoleAvailability
}
interface RoleObserver {
val role: String
val availability: Flow<RoleAvailability>
fun request()
}
fun ComponentActivity.roleObserver(role: String): RoleObserver = DefaultRoleObserver(
roleManager = getSystemService(ComponentActivity.ROLE_SERVICE) as RoleManager,
role = role,
registry = activityResultRegistry,
lifecycle = lifecycle,
)
internal class DefaultRoleObserver(
private val roleManager: RoleManager,
override val role: String,
private val registry: ActivityResultRegistry,
lifecycle: Lifecycle,
) : DefaultLifecycleObserver, RoleObserver {
private lateinit var getRole: ActivityResultLauncher<Intent>
private val updates = MutableStateFlow(System.currentTimeMillis())
override val availability: Flow<RoleAvailability> = updates.map { checkRole(roleManager, role) }
init {
lifecycle.addObserver(this)
}
override fun onCreate(owner: LifecycleOwner) {
getRole = registry.register("$role-request-${UUID.randomUUID()}", owner, ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == ComponentActivity.RESULT_OK) {
updates.trigger()
}
}
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
updates.trigger()
}
override fun request() {
getRole.launch(roleManager.createRequestRoleIntent(role))
}
private fun checkRole(roleManager: RoleManager, role: String) = when {
roleManager.isRoleHeld(role) -> RoleAvailability.Held
roleManager.isRoleAvailable(role) -> RoleAvailability.Available
else -> RoleAvailability.NotAvailable
}
private fun MutableStateFlow<Long>.trigger() {
tryEmit(System.currentTimeMillis())
}
}
class SettingsActivity : ComponentActivity() {
/*
Example usage:
I want my application to act as a call screening app, so I want to set is as default callerID & spam app.
Thus I need ROLE_CALL_SCREENING role.
On start check if role is held, then show app content.
When role is not held but available show some rationale & when request clicked, invoke system popup.
When role is not available, show error.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callScreeningRole = roleObserver(RoleManager.ROLE_CALL_SCREENING)
setContent {
AppTheme {
val callScreeningRoleAvailability by callScreeningRole.availability.collectAsState(RoleAvailability.Held)
when (callScreeningRoleAvailability) {
RoleAvailability.Available -> RequestRoleScreen("Some rationale", onClick = {callScreeningRole.request()})
RoleAvailability.Held -> AppScreen()
RoleAvailability.NotAvailable -> ErrorScreen()
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment