Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
612c017c2b | ||
|
|
a332ca9c23 |
@@ -16,8 +16,8 @@ android {
|
|||||||
applicationId = "app.telefon"
|
applicationId = "app.telefon"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = 3
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release key lives outside the repo; without it, release builds fall back to the debug key (local only).
|
// Release key lives outside the repo; without it, release builds fall back to the debug key (local only).
|
||||||
|
|||||||
@@ -102,14 +102,36 @@ private fun PhoneApp(dialRequest: String?, onDialHandled: () -> Unit) {
|
|||||||
val roleLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
val roleLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
isDefault = role.isRoleHeld(RoleManager.ROLE_DIALER); granted = hasCore(ctx)
|
isDefault = role.isRoleHeld(RoleManager.ROLE_DIALER); granted = hasCore(ctx)
|
||||||
}
|
}
|
||||||
|
var askedRole by rememberSaveable { mutableStateOf(false) }
|
||||||
|
// first start: ask for the role right away; the dialog grants the phone permissions too
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
if (!isDefault && !askedRole && role.isRoleAvailable(RoleManager.ROLE_DIALER)) {
|
||||||
|
askedRole = true
|
||||||
|
roleLauncher.launch(role.createRequestRoleIntent(RoleManager.ROLE_DIALER))
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
if (!granted) perms.launch(arrayOf(Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE,
|
if (!granted) perms.launch(arrayOf(Manifest.permission.READ_CALL_LOG, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE,
|
||||||
Manifest.permission.READ_PHONE_STATE, Manifest.permission.POST_NOTIFICATIONS))
|
Manifest.permission.READ_PHONE_STATE, Manifest.permission.POST_NOTIFICATIONS))
|
||||||
}
|
}
|
||||||
|
|
||||||
val log by remember(granted) { Contacts.callLog(ctx) }.collectAsState(initial = emptyList())
|
val log by remember(granted) { Contacts.callLog(ctx) }.collectAsState(initial = emptyList())
|
||||||
val people by remember(granted) { Contacts.people(ctx) }.collectAsState(initial = emptyList())
|
val people by remember(granted) { Contacts.people(ctx) }.collectAsState(initial = emptyList())
|
||||||
val call: (String) -> Unit = { n -> placeCall(ctx, n) }
|
var pendingNumber by remember { mutableStateOf<String?>(null) }
|
||||||
|
// without the role Android shows the call in the system phone app: say so instead of jumping away
|
||||||
|
val call: (String) -> Unit = { n -> if (isDefault) placeCall(ctx, n) else pendingNumber = n }
|
||||||
|
pendingNumber?.let { n ->
|
||||||
|
androidx.compose.material3.AlertDialog(
|
||||||
|
onDismissRequest = { pendingNumber = null },
|
||||||
|
title = { Text(stringResource(R.string.default_title)) },
|
||||||
|
text = { Text(stringResource(R.string.not_default_call)) },
|
||||||
|
confirmButton = {
|
||||||
|
androidx.compose.material3.TextButton(onClick = { pendingNumber = null; roleLauncher.launch(role.createRequestRoleIntent(RoleManager.ROLE_DIALER)) }) {
|
||||||
|
Text(stringResource(R.string.make_default))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = { androidx.compose.material3.TextButton(onClick = { pendingNumber = null; placeCall(ctx, n) }) { Text(stringResource(R.string.call_anyway)) } },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
screen?.let { s ->
|
screen?.let { s ->
|
||||||
BackHandler { screen = null }
|
BackHandler { screen = null }
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ class CallService : InCallService() {
|
|||||||
|
|
||||||
override fun onCallAdded(call: Call) {
|
override fun onCallAdded(call: Call) {
|
||||||
CallManager.added(call)
|
CallManager.added(call)
|
||||||
if (call.details.state == Call.STATE_RINGING) showIncoming(call) else startActivity(CallActivity.intent(this))
|
// a crash here would make Telecom switch to the system phone app, so failures are logged, not thrown
|
||||||
|
runCatching { if (call.details.state == Call.STATE_RINGING) showIncoming(call) else startActivity(CallActivity.intent(this)) }
|
||||||
|
.onFailure { android.util.Log.e("Telefon", "onCallAdded", it); runCatching { startActivity(CallActivity.intent(this)) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCallRemoved(call: Call) = CallManager.removed(call)
|
override fun onCallRemoved(call: Call) = CallManager.removed(call)
|
||||||
@@ -36,7 +38,9 @@ class CallService : InCallService() {
|
|||||||
updateProximity(audioState.route)
|
updateProximity(audioState.route)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun onCallsChanged(calls: List<CallUi>) {
|
internal fun onCallsChanged(calls: List<CallUi>) = runCatching { updateNotification(calls) }.onFailure { android.util.Log.e("Telefon", "notification", it) }
|
||||||
|
|
||||||
|
private fun updateNotification(calls: List<CallUi>) {
|
||||||
val nm = getSystemService(NotificationManager::class.java)
|
val nm = getSystemService(NotificationManager::class.java)
|
||||||
val ringing = calls.firstOrNull { it.phase == Phase.RINGING }
|
val ringing = calls.firstOrNull { it.phase == Phase.RINGING }
|
||||||
val current = calls.firstOrNull { it.phase != Phase.RINGING && it.phase != Phase.DISCONNECTED }
|
val current = calls.firstOrNull { it.phase != Phase.RINGING && it.phase != Phase.DISCONNECTED }
|
||||||
@@ -64,10 +68,11 @@ class CallService : InCallService() {
|
|||||||
|
|
||||||
private fun ongoing(c: CallUi): Notification {
|
private fun ongoing(c: CallUi): Notification {
|
||||||
val name = Contacts.lookupName(this, c.number) ?: c.number
|
val name = Contacts.lookupName(this, c.number) ?: c.number
|
||||||
val person = Person.Builder().setName(name).build()
|
// not CallStyle: that style is only allowed with a foreground service or a full-screen intent
|
||||||
|
// and throws otherwise, which made Telecom fall back to the system phone app
|
||||||
return NotificationCompat.Builder(this, CH_ONGOING).setSmallIcon(R.drawable.ic_stat_call)
|
return NotificationCompat.Builder(this, CH_ONGOING).setSmallIcon(R.drawable.ic_stat_call)
|
||||||
.setContentTitle(name).setContentIntent(CallActivity.pending(this)).setOngoing(true).setSilent(true)
|
.setContentTitle(name).setContentText(getString(R.string.ongoing_call)).setContentIntent(CallActivity.pending(this)).setOngoing(true).setSilent(true)
|
||||||
.setStyle(NotificationCompat.CallStyle.forOngoingCall(person, action(ACTION_HANGUP)))
|
.addAction(0, getString(R.string.hang_up), action(ACTION_HANGUP))
|
||||||
.apply { c.connectedAt?.let { setUsesChronometer(true).setWhen(it).setShowWhen(true) } }
|
.apply { c.connectedAt?.let { setUsesChronometer(true).setWhen(it).setShowWhen(true) } }
|
||||||
.setCategory(NotificationCompat.CATEGORY_CALL).build()
|
.setCategory(NotificationCompat.CATEGORY_CALL).build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
<string name="default_title">Als Standard-Telefon-App festlegen</string>
|
<string name="default_title">Als Standard-Telefon-App festlegen</string>
|
||||||
<string name="default_text">Nur dann kann diese App Anrufe annehmen und anzeigen. Du kannst das jederzeit in den Android-Einstellungen ändern.</string>
|
<string name="default_text">Nur dann kann diese App Anrufe annehmen und anzeigen. Du kannst das jederzeit in den Android-Einstellungen ändern.</string>
|
||||||
|
|
||||||
|
<string name="not_default_call">Diese App ist noch nicht deine Standard-Telefon-App. Android zeigt den Anruf deshalb in der System-Telefon-App an.</string>
|
||||||
|
<string name="make_default">Als Standard festlegen</string>
|
||||||
|
<string name="call_anyway">Trotzdem anrufen</string>
|
||||||
<string name="incoming_call">Eingehender Anruf</string>
|
<string name="incoming_call">Eingehender Anruf</string>
|
||||||
<string name="calling">Wird angerufen …</string>
|
<string name="calling">Wird angerufen …</string>
|
||||||
<string name="on_hold">Gehalten</string>
|
<string name="on_hold">Gehalten</string>
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
<string name="resume">Fortsetzen</string>
|
<string name="resume">Fortsetzen</string>
|
||||||
<string name="bluetooth">Bluetooth</string>
|
<string name="bluetooth">Bluetooth</string>
|
||||||
<string name="hang_up">Auflegen</string>
|
<string name="hang_up">Auflegen</string>
|
||||||
|
<string name="ongoing_call">Laufender Anruf</string>
|
||||||
<string name="ch_incoming">Eingehende Anrufe</string>
|
<string name="ch_incoming">Eingehende Anrufe</string>
|
||||||
<string name="ch_ongoing">Laufende Anrufe</string>
|
<string name="ch_ongoing">Laufende Anrufe</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user