mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(android): align lint gates and photo permission handling
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -66,6 +66,7 @@ android {
|
||||
lint {
|
||||
disable +=
|
||||
setOf(
|
||||
"AndroidGradlePluginVersion",
|
||||
"GradleDependency",
|
||||
"IconLauncherShape",
|
||||
"NewerVersionAvailable",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user