From fa9148400e53caa81f13e65dee02ab200c3eebbb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 2 Mar 2026 04:27:33 +0000 Subject: [PATCH] fix(android): align lint gates and photo permission handling --- CHANGELOG.md | 9 +++- apps/android/app/build.gradle.kts | 1 + apps/android/app/src/main/AndroidManifest.xml | 1 + .../android/node/CameraCaptureManager.kt | 2 + .../ai/openclaw/android/node/DeviceHandler.kt | 9 +--- .../node/DeviceNotificationListenerService.kt | 4 -- .../ai/openclaw/android/node/MotionHandler.kt | 2 - .../ai/openclaw/android/node/PhotosHandler.kt | 5 +- .../ai/openclaw/android/node/SystemHandler.kt | 3 -- .../ai/openclaw/android/ui/OnboardingFlow.kt | 7 ++- .../ai/openclaw/android/ui/SettingsSheet.kt | 40 +++++++--------- .../openclaw/android/voice/TalkModeManager.kt | 48 +++++++------------ 12 files changed, 54 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe02f6db223..8723e2760b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,10 @@ Docs: https://docs.openclaw.ai - LINE/Voice transcription: classify M4A voice media as `audio/mp4` (not `video/mp4`) by checking the MPEG-4 `ftyp` major brand (`M4A ` / `M4B `), restoring voice transcription for LINE voice messages. Landed from contributor PR #31151 by @scoootscooob. Thanks @scoootscooob. - Slack/Announce target account routing: enable session-backed announce-target lookup for Slack so multi-account announces resolve the correct `accountId` instead of defaulting to bot-token context. Landed from contributor PR #31028 by @taw0002. Thanks @taw0002. - Android/Voice screen TTS: stream assistant speech via ElevenLabs WebSocket in Talk Mode, stop cleanly on speaker mute/barge-in, and ignore stale out-of-order stream events. (#29521) Thanks @gregmousseau. +- Android/Photos permissions: declare Android 14+ selected-photo access permission (`READ_MEDIA_VISUAL_USER_SELECTED`) and align Android permission/settings paths with current minSdk behavior for more reliable permission state handling. +- Web UI/Cron: include configured agent model defaults/fallbacks in cron model suggestions so scheduled-job model autocomplete reflects configured models. (#29709) Thanks @Sid-Qin. +- Cron/Delivery: disable the agent messaging tool when `delivery.mode` is `"none"` so cron output is not sent to Telegram or other channels. (#21808) Thanks @lailoo. +- CLI/Cron: clarify `cron list` output by renaming `Agent` to `Agent ID` and adding a `Model` column for isolated agent-turn jobs. (#26259) Thanks @openperf. - Feishu/Reply media attachments: send Feishu reply `mediaUrl`/`mediaUrls` payloads as attachments alongside text/streamed replies in the reply dispatcher, including legacy fallback when `mediaUrls` is empty. (#28959) Thanks @icesword0760. - Slack/User-token resolution: normalize Slack account user-token sourcing through resolved account metadata (`SLACK_USER_TOKEN` env + config) so monitor reads, Slack actions, directory lookups, onboarding allow-from resolution, and capabilities probing consistently use the effective user token. (#28103) Thanks @Glucksberg. - Feishu/Outbound session routing: stop assuming bare `oc_` identifiers are always group chats, honor explicit `dm:`/`group:` prefixes for `oc_` chat IDs, and default ambiguous bare `oc_` targets to direct routing to avoid DM session misclassification. (#10407) Thanks @Bermudarat. @@ -118,7 +122,10 @@ Docs: https://docs.openclaw.ai - Sandbox/mkdirp boundary checks: allow directory-safe boundary validation for existing in-boundary subdirectories, preventing false `cannot create directories` failures in sandbox write mode. (#30610) Thanks @glitch418x. - Security/Compaction audit: remove the post-compaction audit injection message. (#28507) Thanks @fuller-stack-dev and @vincentkoc. - Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (`198.18.0.0/15`) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux. - +- Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger. +- Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted `System:` context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky. +- Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff. +- Feishu/Typing replay suppression: skip typing indicators for stale replayed inbound messages after compaction using message-age checks with second/millisecond timestamp normalization, preventing old-message reaction floods while preserving typing for fresh messages. Landed from contributor PR #30709 by @arkyu2077. Thanks @arkyu2077. ## Unreleased ### Changes diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index a2076b99cf6..0f0a78d51d6 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -66,6 +66,7 @@ android { lint { disable += setOf( + "AndroidGradlePluginVersion", "GradleDependency", "IconLauncherShape", "NewerVersionAvailable", diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index f5e20fd5a97..0507bdf8aa1 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/CameraCaptureManager.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/CameraCaptureManager.kt index 0dc6f2b6955..87572b37ad8 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/CameraCaptureManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/CameraCaptureManager.kt @@ -359,6 +359,7 @@ class CameraCaptureManager(private val context: Context) { .build() } + @SuppressLint("UnsafeOptInUsageError") private fun cameraDeviceInfoOrNull(info: CameraInfo): CameraDeviceInfo? { val cameraId = cameraIdOrNull(info) ?: return null val lensFacing = @@ -389,6 +390,7 @@ class CameraCaptureManager(private val context: Context) { ) } + @SuppressLint("UnsafeOptInUsageError") private fun cameraIdOrNull(info: CameraInfo): String? = runCatching { Camera2CameraInfo.from(info).cameraId }.getOrNull() } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceHandler.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceHandler.kt index a091b6f211b..4c7045b4608 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceHandler.kt @@ -136,12 +136,7 @@ class DeviceHandler( } else { hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE) } - val motionGranted = - if (Build.VERSION.SDK_INT >= 29) { - hasPermission(Manifest.permission.ACTIVITY_RECOGNITION) - } else { - true - } + val motionGranted = hasPermission(Manifest.permission.ACTIVITY_RECOGNITION) val notificationsGranted = if (Build.VERSION.SDK_INT >= 33) { hasPermission(Manifest.permission.POST_NOTIFICATIONS) @@ -228,7 +223,7 @@ class DeviceHandler( "motion", permissionStateJson( granted = motionGranted, - promptableWhenDenied = Build.VERSION.SDK_INT >= 29, + promptableWhenDenied = true, ), ) // Screen capture on Android is interactive per-capture consent, not a sticky app permission. diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceNotificationListenerService.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceNotificationListenerService.kt index 0663bb8158d..4a2ce7a9a78 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceNotificationListenerService.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/DeviceNotificationListenerService.kt @@ -6,7 +6,6 @@ import android.app.RemoteInput import android.content.ComponentName import android.content.Context import android.content.Intent -import android.os.Build import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import kotlinx.serialization.json.JsonPrimitive @@ -234,9 +233,6 @@ class DeviceNotificationListenerService : NotificationListenerService() { } fun requestServiceRebind(context: Context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - return - } runCatching { NotificationListenerService.requestRebind(serviceComponent(context)) } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/MotionHandler.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/MotionHandler.kt index d385b35f182..52658f8efb6 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/MotionHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/MotionHandler.kt @@ -6,7 +6,6 @@ import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager -import android.os.Build import android.os.SystemClock import androidx.core.content.ContextCompat import ai.openclaw.android.gateway.GatewaySession @@ -85,7 +84,6 @@ private object SystemMotionDataSource : MotionDataSource { } override fun hasPermission(context: Context): Boolean { - if (Build.VERSION.SDK_INT < 29) return true return ContextCompat.checkSelfPermission(context, Manifest.permission.ACTIVITY_RECOGNITION) == android.content.pm.PackageManager.PERMISSION_GRANTED } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/PhotosHandler.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/PhotosHandler.kt index 3b9c0199d06..e7f3debff06 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/PhotosHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/PhotosHandler.kt @@ -11,6 +11,7 @@ import android.os.Build import android.os.Bundle import android.provider.MediaStore import androidx.core.content.ContextCompat +import androidx.core.graphics.scale import ai.openclaw.android.gateway.GatewaySession import java.io.ByteArrayOutputStream import java.time.Instant @@ -158,7 +159,7 @@ private object SystemPhotosDataSource : PhotosDataSource { if (decoded.width <= maxWidth) return decoded val targetHeight = max(1, ((decoded.height.toDouble() * maxWidth) / decoded.width).roundToInt()) - return Bitmap.createScaledBitmap(decoded, maxWidth, targetHeight, true) + return decoded.scale(maxWidth, targetHeight, true) } private fun computeInSampleSize(width: Int, maxWidth: Int): Int { @@ -198,7 +199,7 @@ private object SystemPhotosDataSource : PhotosDataSource { val nextWidth = max(240, (working.width * 0.75f).roundToInt()) if (nextWidth >= working.width) return null val nextHeight = max(1, ((working.height.toDouble() * nextWidth) / working.width).roundToInt()) - working = Bitmap.createScaledBitmap(working, nextWidth, nextHeight, true) + working = working.scale(nextWidth, nextHeight, true) } return null } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt b/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt index 6d8fd5b86aa..ee794f7ac4e 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/node/SystemHandler.kt @@ -67,9 +67,6 @@ private class AndroidSystemNotificationPoster( } private fun ensureChannel(priority: String?): String { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - return NOTIFICATION_CHANNEL_BASE_ID - } val normalizedPriority = priority.orEmpty().trim().lowercase() val (suffix, importance, name) = when (normalizedPriority) { diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt index 1a25cce68d3..cc596706ec0 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt @@ -80,6 +80,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner @@ -242,7 +243,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) { remember(context) { hasMotionCapabilities(context) } - val motionPermissionRequired = Build.VERSION.SDK_INT >= 29 + val motionPermissionRequired = true val notificationsPermissionRequired = Build.VERSION.SDK_INT >= 33 val discoveryPermission = if (Build.VERSION.SDK_INT >= 33) { @@ -1635,7 +1636,6 @@ private fun isNotificationListenerEnabled(context: Context): Boolean { } private fun canInstallUnknownApps(context: Context): Boolean { - if (Build.VERSION.SDK_INT < 26) return true return context.packageManager.canRequestPackageInstalls() } @@ -1649,11 +1649,10 @@ private fun openNotificationListenerSettings(context: Context) { } private fun openUnknownAppSourcesSettings(context: Context) { - if (Build.VERSION.SDK_INT < 26) return val intent = Intent( Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, - Uri.parse("package:${context.packageName}"), + "package:${context.packageName}".toUri(), ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) runCatching { context.startActivity(intent) diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt index b20013f7d57..cd1368db1b4 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt @@ -62,6 +62,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner @@ -171,7 +172,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } else { Manifest.permission.READ_EXTERNAL_STORAGE } - val motionPermissionRequired = Build.VERSION.SDK_INT >= 29 + val motionPermissionRequired = true val motionAvailable = remember(context) { hasMotionCapabilities(context) } var notificationsPermissionGranted by @@ -424,7 +425,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Microphone permission", style = mobileHeadline) }, supportingContent = { @@ -477,7 +478,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Allow Camera", style = mobileHeadline) }, supportingContent = { Text("Allows the gateway to request photos or short video clips (foreground only).", style = mobileCallout) }, @@ -510,7 +511,7 @@ fun SettingsSheet(viewModel: MainViewModel) { else -> "Grant" } ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("SMS Permission", style = mobileHeadline) }, supportingContent = { @@ -561,7 +562,7 @@ fun SettingsSheet(viewModel: MainViewModel) { "Grant" } ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("System Notifications", style = mobileHeadline) }, supportingContent = { @@ -589,7 +590,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Notification Listener Access", style = mobileHeadline) }, supportingContent = { @@ -624,7 +625,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Photos Permission", style = mobileHeadline) }, supportingContent = { @@ -655,7 +656,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Contacts Permission", style = mobileHeadline) }, supportingContent = { @@ -686,7 +687,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Calendar Permission", style = mobileHeadline) }, supportingContent = { @@ -724,7 +725,7 @@ fun SettingsSheet(viewModel: MainViewModel) { else -> "Grant" } ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Motion Permission", style = mobileHeadline) }, supportingContent = { @@ -768,7 +769,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Install App Updates", style = mobileHeadline) }, supportingContent = { @@ -802,7 +803,7 @@ fun SettingsSheet(viewModel: MainViewModel) { ) } item { - Column(modifier = settingsRowModifier(), verticalArrangement = Arrangement.spacedBy(0.dp)) { + Column(modifier = Modifier.settingsRowModifier(), verticalArrangement = Arrangement.spacedBy(0.dp)) { ListItem( modifier = Modifier.fillMaxWidth(), colors = listItemColors, @@ -877,7 +878,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Prevent Sleep", style = mobileHeadline) }, supportingContent = { Text("Keeps the screen awake while OpenClaw is open.", style = mobileCallout) }, @@ -897,7 +898,7 @@ fun SettingsSheet(viewModel: MainViewModel) { } item { ListItem( - modifier = settingsRowModifier(), + modifier = Modifier.settingsRowModifier(), colors = listItemColors, headlineContent = { Text("Debug Canvas Status", style = mobileHeadline) }, supportingContent = { Text("Show status text in the canvas when debug is enabled.", style = mobileCallout) }, @@ -927,8 +928,8 @@ private fun settingsTextFieldColors() = cursorColor = mobileAccent, ) -private fun settingsRowModifier() = - Modifier +private fun Modifier.settingsRowModifier() = + this .fillMaxWidth() .border(width = 1.dp, color = mobileBorder, shape = RoundedCornerShape(14.dp)) .background(Color.White, RoundedCornerShape(14.dp)) @@ -970,14 +971,10 @@ private fun openNotificationListenerSettings(context: Context) { } private fun openUnknownAppSourcesSettings(context: Context) { - if (Build.VERSION.SDK_INT < 26) { - openAppSettings(context) - return - } val intent = Intent( Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, - Uri.parse("package:${context.packageName}"), + "package:${context.packageName}".toUri(), ) runCatching { context.startActivity(intent) @@ -997,7 +994,6 @@ private fun isNotificationListenerEnabled(context: Context): Boolean { } private fun canInstallUnknownApps(context: Context): Boolean { - if (Build.VERSION.SDK_INT < 26) return true return context.packageManager.canRequestPackageInstalls() } diff --git a/apps/android/app/src/main/java/ai/openclaw/android/voice/TalkModeManager.kt b/apps/android/app/src/main/java/ai/openclaw/android/voice/TalkModeManager.kt index 8bafd603b85..3b20b4f5429 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/voice/TalkModeManager.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/voice/TalkModeManager.kt @@ -24,7 +24,6 @@ import androidx.core.content.ContextCompat import ai.openclaw.android.gateway.GatewaySession import ai.openclaw.android.isCanonicalMainSessionKey import ai.openclaw.android.normalizeMainKey -import android.os.Build import java.io.File import java.net.HttpURLConnection import java.net.URL @@ -1316,43 +1315,28 @@ private const val defaultTalkProvider = "elevenlabs" private fun requestAudioFocusForTts(): Boolean { val am = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: return true - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) - .setAudioAttributes( - AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_MEDIA) - .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) - .build() - ) - .setOnAudioFocusChangeListener(audioFocusListener) - .build() - audioFocusRequest = req - val result = am.requestAudioFocus(req) - Log.d(tag, "audio focus request result=$result") - result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED || result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED - } else { - @Suppress("DEPRECATION") - val result = am.requestAudioFocus( - audioFocusListener, - AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + val req = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) + .build() ) - result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - } + .setOnAudioFocusChangeListener(audioFocusListener) + .build() + audioFocusRequest = req + val result = am.requestAudioFocus(req) + Log.d(tag, "audio focus request result=$result") + return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED || result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED } private fun abandonAudioFocus() { val am = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - audioFocusRequest?.let { - am.abandonAudioFocusRequest(it) - Log.d(tag, "audio focus abandoned") - } - audioFocusRequest = null - } else { - @Suppress("DEPRECATION") - am.abandonAudioFocus(audioFocusListener) + audioFocusRequest?.let { + am.abandonAudioFocusRequest(it) + Log.d(tag, "audio focus abandoned") } + audioFocusRequest = null } private fun cleanupPlayer() {