From dff22c9e3091b22c2a2662416a77747daa0a92f0 Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Fri, 8 Aug 2025 21:41:27 +0800 Subject: [PATCH 01/20] =?UTF-8?q?Feat:=20Add=20media=20output=20switcher?= =?UTF-8?q?=20support=20for=20original=20equipment=20manufacturers=20(OEMs?= =?UTF-8?q?)=20Code=20sources:=20https://github.com/Moriafly/media-kit=20a?= =?UTF-8?q?nd=20https://github.com/Yos-X/FlamingoSank=20The=20functionalit?= =?UTF-8?q?y=20works=20as=20follows=20across=20different=20Android=20versi?= =?UTF-8?q?ons=20and=20OEM=20ROMs:=20-=20For=20Android=2014=20and=20higher?= =?UTF-8?q?,=20it=20uses=20MediaRouter2#showSystemOutputSwitcher.=20-=20Fo?= =?UTF-8?q?r=20Android=2012=20and=20higher,=20it=20uses=20the=20=E2=80=9Cc?= =?UTF-8?q?om.android.systemui.action.LAUNCH=5FMEDIA=5FOUTPUT=5FDIALOG?= =?UTF-8?q?=E2=80=9D=20action.=20-=20For=20Android=2011,=20it=20uses=20the?= =?UTF-8?q?=20=E2=80=9Ccom.android.settings.panel.action.MEDIA=5FOUTPUT?= =?UTF-8?q?=E2=80=9D=20action.=20-=20For=20MIUI=20(Xiaomi),=20it=20uses=20?= =?UTF-8?q?=E2=80=9Cmiui.systemui.miplay.MiPlayDetailActivity=E2=80=9D.=20?= =?UTF-8?q?-=20For=20One=20UI=20(Samsung),=20it=20uses=20=E2=80=9Ccom.sams?= =?UTF-8?q?ung.android.mdx.quickboard.view.MediaActivity=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../utils/exoplayer/oem/MiPlayAudioSupport.kt | 123 ++++++++++++++++++ .../oem/SystemMediaControlResolver.kt | 115 ++++++++++++++++ .../exoplayer/oem/UnstableMediaKitApi.kt | 26 ++++ .../ui/components/FullBottomSheet.kt | 8 ++ .../main/res/drawable/ic_media_control.png | Bin 0 -> 1203 bytes app/src/main/res/layout/full_player.xml | 36 +++++ 6 files changed, 308 insertions(+) create mode 100644 app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt create mode 100644 app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt create mode 100644 app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt create mode 100644 app/src/main/res/drawable/ic_media_control.png diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt new file mode 100644 index 0000000000..9a13cfb638 --- /dev/null +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt @@ -0,0 +1,123 @@ +@file:Suppress("unused", "SpellCheckingInspection") + +package org.akanework.gramophone.logic.utils.exoplayer.oem + +/** + * Media Kit + * Copyright (C) 2025 Moriafly + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + + + +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager + +/** + * 小米妙播适配 + */ +@UnstableMediaKitApi +object MiPlayAudioSupport { + private const val ACTION_MIPLAY_DETAIL = "miui.intent.action.ACTIVITY_MIPLAY_DETAIL" + private const val AUDIO_RECORD_CLASS = "miui.media.MiuiAudioPlaybackRecorder" + private const val PACKAGE_NAME = "com.milink.service" + private const val SERVICE_NAME = "com.miui.miplay.audio.service.CoreService" + private const val WHITE_TARGET = "com.milink.service:hide_foreground" + + /** + * 查询是否支持妙播服务 + * + * https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1944 + */ + fun supportMiPlay(context: Context): Boolean { + try { + // 未找到抛出 PackageManager.NameNotFoundException + context.packageManager.getServiceInfo( + ComponentName(PACKAGE_NAME, SERVICE_NAME), + PackageManager.MATCH_ALL + ) + // 未找到抛出 ClassNotFoundException + context.classLoader.loadClass(AUDIO_RECORD_CLASS) + + val isInternationalBuild = isInternationalBuild() + val systemUIReady = systemUIReady(context) + val notificationReady = notificationReady(context) + return !isInternationalBuild && systemUIReady && notificationReady + } catch (_: Exception) { + return false + } + } + + /** + * 是否为国际版 + */ + private fun isInternationalBuild(): Boolean = + try { + val clazz = Class.forName("miui.os.Build") + val field = clazz.getField("IS_INTERNATIONAL_BUILD") + field.isAccessible = true + field.getBoolean(null) + } catch (_: Exception) { + false + } + + /** + * 检查 SystemUI 是否包含处理妙播意图的 Activity + */ + private fun systemUIReady(context: Context): Boolean { + val intent = + Intent(ACTION_MIPLAY_DETAIL).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + // TODO 是否需要 try catch? + return try { + context.packageManager + .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null + } catch (_: ActivityNotFoundException) { + false + } + } + + /** + * 检查妙播服务是否在 SystemUI 的前台服务通知白名单中 + */ + private fun notificationReady(context: Context): Boolean = + try { + val systemUiAppInfo = + context.packageManager.getApplicationInfo( + "com.android.systemui", + 0 + ) + val resources = context.packageManager.getResourcesForApplication(systemUiAppInfo) + val identifier = + @SuppressLint("DiscouragedApi") + resources.getIdentifier( + "system_foreground_notification_whitelist", + "array", + "com.android.systemui" + ) + + if (identifier > 0) { + val whiteList = resources.getStringArray(identifier) + val contains = whiteList.contains(WHITE_TARGET) + contains + } else { + false + } + } catch (_: Exception) { + false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt new file mode 100644 index 0000000000..6646f8f94d --- /dev/null +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -0,0 +1,115 @@ +package org.akanework.gramophone.logic.utils.exoplayer.oem + +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.ResolveInfo +import android.media.MediaRouter2 +import android.os.Build +import androidx.annotation.RequiresApi +import org.akanework.gramophone.logic.utils.exoplayer.oem.MiPlayAudioSupport.supportMiPlay + +class SystemMediaControlResolver(val context: Context) { + @OptIn(UnstableMediaKitApi::class) + fun intentSystemMediaDialog() { + val manufacturer = Build.MANUFACTURER.lowercase() + when { + manufacturer.contains("xiaomi") -> { + if (supportMiPlay(context)){ + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + setClassName( + "miui.systemui.plugin", + "miui.systemui.miplay.MiPlayDetailActivity" + ) + } + startIntent(intent) + } + } + + manufacturer.contains("samsung") -> { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + setClassName( + "com.samsung.android.mdx.quickboard", + "com.samsung.android.mdx.quickboard.view.MediaActivity" + ) + } + startIntent(intent) + } + + else -> { + if (Build.VERSION.SDK_INT >= 34) { + // zh: Android 14 及以上 + startNativeMediaDialogForAndroid14(context) + } else if (Build.VERSION.SDK_INT >= 31) { + // zh: Android 12 及以上 + val intent = Intent().apply { + action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" + setPackage("com.android.systemui") + putExtra("package_name", context.packageName) + } + startNativeMediaDialog(intent) + } else if (Build.VERSION.SDK_INT == 30) { + // Android 11 + startNativeMediaDialogForAndroid11(context) + } else { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + startNativeMediaDialog(intent) + } + } + } + } + + private fun startNativeMediaDialog(intent: Intent): Boolean { + val resolveInfoList: List = + context.packageManager.queryIntentActivities(intent, 0) + for (resolveInfo in resolveInfoList) { + val activityInfo = resolveInfo.activityInfo + val applicationInfo: ApplicationInfo? = activityInfo?.applicationInfo + if (applicationInfo != null && (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + context.startActivity(intent) + return true + } + } + return false + } + + private fun startNativeMediaDialogForAndroid11(context: Context): Boolean { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + val resolveInfoList: List = + context.packageManager.queryIntentActivities(intent, 0) + for (resolveInfo in resolveInfoList) { + val activityInfo = resolveInfo.activityInfo + val applicationInfo: ApplicationInfo? = activityInfo?.applicationInfo + if (applicationInfo != null && (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + context.startActivity(intent) + return true + } + } + return false + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private fun startNativeMediaDialogForAndroid14(context: Context): Boolean { + val mediaRouter2 = MediaRouter2.getInstance(context) + return mediaRouter2.showSystemOutputSwitcher() + } + + private fun startIntent(intent: Intent): Boolean { + return try { + context.startActivity(intent) + true + } catch (_: Exception) { + false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt new file mode 100644 index 0000000000..53628ed9dd --- /dev/null +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt @@ -0,0 +1,26 @@ +@file:Suppress("unused") + +package org.akanework.gramophone.logic.utils.exoplayer.oem + +/** + * Media Kit + * Copyright (C) 2025 Moriafly + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +@RequiresOptIn( + message = "This Media Kit API is experimental and is likely to change or be removed in the " + + "future", + level = RequiresOptIn.Level.ERROR, +) +@Retention(AnnotationRetention.BINARY) +annotation class UnstableMediaKitApi \ No newline at end of file diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 2391133575..08d32cafb7 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -93,6 +93,8 @@ import org.akanework.gramophone.logic.utils.AudioFormatDetector.SpatialFormat import org.akanework.gramophone.logic.utils.CalculationUtils import org.akanework.gramophone.logic.utils.ColorUtils import org.akanework.gramophone.logic.utils.Flags +import org.akanework.gramophone.logic.utils.convertDurationToTimeStamp +import org.akanework.gramophone.logic.utils.exoplayer.oem.SystemMediaControlResolver import org.akanework.gramophone.ui.MainActivity import org.akanework.gramophone.ui.fragments.ArtistSubFragment import org.akanework.gramophone.ui.fragments.DetailDialogFragment @@ -199,6 +201,7 @@ class FullBottomSheet private val bottomSheetShuffleButton: MaterialButton private val bottomSheetLoopButton: MaterialButton private val bottomSheetPlaylistButton: MaterialButton + private val bottomSheetMediaControl: MaterialButton private val bottomSheetTimerButton: MaterialButton private val bottomSheetPlaybackSpeedButton: MaterialButton private val bottomSheetFavoriteButton: MaterialButton @@ -237,6 +240,7 @@ class FullBottomSheet if (!Flags.FAVORITE_SONGS) bottomSheetFavoriteButton.visibility = GONE bottomSheetPlaylistButton = findViewById(R.id.playlist) + bottomSheetMediaControl = findViewById(R.id.media_control) bottomSheetLyricButton = findViewById(R.id.lyrics) bottomSheetFullLyricView = findViewById(R.id.lyric_frame) bottomSheetFullQualityDetails = findViewById(R.id.quality_details) @@ -412,6 +416,10 @@ class FullBottomSheet bottomSheetFavoriteButton.addOnCheckedChangeListener(this) + bottomSheetMediaControl.setOnClickListener { + SystemMediaControlResolver(context).intentSystemMediaDialog() + } + bottomSheetPlaylistButton.setOnClickListener { ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) if (instance != null) diff --git a/app/src/main/res/drawable/ic_media_control.png b/app/src/main/res/drawable/ic_media_control.png new file mode 100644 index 0000000000000000000000000000000000000000..0cc2b20ca622fb6b90dfb6041476f4c9748119d9 GIT binary patch literal 1203 zcmV;k1WfyhP)y000DfNklteDhy>=UmAVl$L0q_U0tqCLKmrLQ zkU$t?NJD{f%rMf-0>bmq}wpCoRljq?iD_f={sJ z2q7ahLpUO+pKv8sz$|HLrsEtwy{weZb9}=MGi{$niud+iCjXIp^AAJTJQ>yTGd6pbHaVl^{q-I4+Ycb=D2& zDp}vhC2bX#K;JpnDs+cSn5!iHI9d0vQcWCZSHtY!J}P;6MxJ{@^GYH~D8~~#K{=9C z`p`Vnw5uiVY>?cl#ahzQD3TQkMY47+qU}PFtSI&(nb)%8Yb~O$jh7ug z7jm1mh&CH9 zW4<}G_0ci7@?ya~V>Is@yI7!HqdyxXx@7RG24m9YmwjVIe;K{}ay9|%9q1clMBf^| zPIR`YcHj`el(-p-0W*v_0Kd^??1GlHd(1T+p7IP|c?%?vKmrLQkU)GZe*tOxbJNj) Rm6QMg002ovPDHLkV1m6&Egt{? literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/full_player.xml b/app/src/main/res/layout/full_player.xml index 91bee337d1..94ecc286d9 100644 --- a/app/src/main/res/layout/full_player.xml +++ b/app/src/main/res/layout/full_player.xml @@ -429,6 +429,42 @@ app:toggleCheckedStateOnClick="false" app:tooltipText="@string/timer" /> + + + + Date: Sat, 9 Aug 2025 12:12:45 +0800 Subject: [PATCH 02/20] Fix: Refactored the SystemMediaControlResolver The SystemMediaControlResolver has been refactored to better handle various Android versions and OEM customizations. It also introduces dedicated checks for One UI versions and displays a Toast message when media output settings cannot be opened. Specific changes include: * The logic for launching the system media control dialog has been refactored into a new private function `startSystemMediaControl()`. * Added a new function `getOneUIVersionReadable()` to retrieve the One UI version. * Fall back to `startSystemMediaControl()` when launching OEM-specific media control dialogs fails. * Display a Toast message when media output settings cannot be opened. * Added new strings in `strings.xml`. --- .../oem/SystemMediaControlResolver.kt | 113 ++++++++++++------ 1 file changed, 77 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index 6646f8f94d..cf44316e66 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -1,33 +1,35 @@ package org.akanework.gramophone.logic.utils.exoplayer.oem +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.ResolveInfo import android.media.MediaRouter2 import android.os.Build +import android.widget.Toast import androidx.annotation.RequiresApi +import org.akanework.gramophone.R import org.akanework.gramophone.logic.utils.exoplayer.oem.MiPlayAudioSupport.supportMiPlay class SystemMediaControlResolver(val context: Context) { @OptIn(UnstableMediaKitApi::class) fun intentSystemMediaDialog() { - val manufacturer = Build.MANUFACTURER.lowercase() +// val manufacturer = Build.MANUFACTURER.lowercase() when { - manufacturer.contains("xiaomi") -> { - if (supportMiPlay(context)){ - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - setClassName( - "miui.systemui.plugin", - "miui.systemui.miplay.MiPlayDetailActivity" - ) - } - startIntent(intent) + supportMiPlay(context) -> { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + setClassName( + "miui.systemui.plugin", + "miui.systemui.miplay.MiPlayDetailActivity" + ) + } + if (!startIntent(intent)) { + startSystemMediaControl() } } - - manufacturer.contains("samsung") -> { + (getOneUIVersionReadable() != null) -> { val intent = Intent().apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK setClassName( @@ -35,32 +37,50 @@ class SystemMediaControlResolver(val context: Context) { "com.samsung.android.mdx.quickboard.view.MediaActivity" ) } - startIntent(intent) + if (!startIntent(intent)) { + startSystemMediaControl() + } } else -> { - if (Build.VERSION.SDK_INT >= 34) { - // zh: Android 14 及以上 - startNativeMediaDialogForAndroid14(context) - } else if (Build.VERSION.SDK_INT >= 31) { - // zh: Android 12 及以上 - val intent = Intent().apply { - action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" - setPackage("com.android.systemui") - putExtra("package_name", context.packageName) - } - startNativeMediaDialog(intent) - } else if (Build.VERSION.SDK_INT == 30) { - // Android 11 - startNativeMediaDialogForAndroid11(context) - } else { - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = "com.android.settings.panel.action.MEDIA_OUTPUT" - putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) - } - startNativeMediaDialog(intent) - } + startSystemMediaControl() + } + } + } + + private fun startSystemMediaControl(){ + if (Build.VERSION.SDK_INT >= 34) { + // zh: Android 14 及以上 + val tag = startNativeMediaDialogForAndroid14(context) + if (!tag) { + Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() + } + } else if (Build.VERSION.SDK_INT >= 31) { + // zh: Android 12 及以上 + val intent = Intent().apply { + action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" + setPackage("com.android.systemui") + putExtra("package_name", context.packageName) + } + val tag = startNativeMediaDialog(intent) + if (!tag) { + Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() + } + } else if (Build.VERSION.SDK_INT == 30) { + // Android 11 + val tag = startNativeMediaDialogForAndroid11(context) + if (!tag) { + Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() + } + } else { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + val tag = startNativeMediaDialog(intent) + if (!tag) { + Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } } } @@ -112,4 +132,25 @@ class SystemMediaControlResolver(val context: Context) { false } } + + /** + * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null + * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property + */ + @SuppressLint("PrivateApi") + fun getOneUIVersionReadable(): String? { + return try { + val systemProperties = Class.forName("android.os.SystemProperties") + val get = systemProperties.getMethod("get", String::class.java) + val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() + if (value.isEmpty()) return null + val code = value.toIntOrNull() ?: return null + val major = code / 10000 + val minor = (code / 100) % 100 + val patch = code % 100 + "$major.$minor.$patch" + } catch (e: Exception) { + null + } + } } \ No newline at end of file From 72481fe5bc588aaf819797ffc6131f06cb721d8f Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Sat, 9 Aug 2025 15:03:17 +0800 Subject: [PATCH 03/20] Refactor: Improve One UI version detection Moved the `getSystemProperties` logic into a separate private function for better readability and potential reuse. This change also ensures that an `IllegalArgumentException` is properly thrown if encountered during reflection. --- .../oem/SystemMediaControlResolver.kt | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index cf44316e66..cc4ad728b0 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -137,12 +137,9 @@ class SystemMediaControlResolver(val context: Context) { * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property */ - @SuppressLint("PrivateApi") - fun getOneUIVersionReadable(): String? { - return try { - val systemProperties = Class.forName("android.os.SystemProperties") - val get = systemProperties.getMethod("get", String::class.java) - val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() + private fun getOneUIVersionReadable(): String? { + return try {1 + val value = getSystemProperties("ro.build.version.oneui") if (value.isEmpty()) return null val code = value.toIntOrNull() ?: return null val major = code / 10000 @@ -153,4 +150,17 @@ class SystemMediaControlResolver(val context: Context) { null } } + + @SuppressLint("PrivateApi") + private fun getSystemProperties(key: String): String { + val ret: String = try { + Class.forName("android.os.SystemProperties").getDeclaredMethod("get", String::class.java).invoke(null, key) as String + } catch (iAE: IllegalArgumentException) { + throw iAE + } catch (e: Exception) { + "" + } + return ret + } + } \ No newline at end of file From d75f0383cee649a262d3d34b61521805c556a7be Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Tue, 12 Aug 2025 14:13:09 +0800 Subject: [PATCH 04/20] =?UTF-8?q?fix=EF=BC=9AMerge=20code=20for=20calling?= =?UTF-8?q?=20media=20controls=20on=20Android=2011=20and=20lower=20version?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exoplayer/oem/SystemMediaControlResolver.kt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index cc4ad728b0..845782b3f6 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -66,19 +66,9 @@ class SystemMediaControlResolver(val context: Context) { if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } - } else if (Build.VERSION.SDK_INT == 30) { - // Android 11 - val tag = startNativeMediaDialogForAndroid11(context) - if (!tag) { - Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() - } } else { - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = "com.android.settings.panel.action.MEDIA_OUTPUT" - putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) - } - val tag = startNativeMediaDialog(intent) + // zh: Android 11 及以下 + val tag = startNativeMediaDialogForAndroid11(context) if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } From b23f50c7f34e20935a8da4a2533fb77c6f331d0f Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Sat, 9 Aug 2025 12:12:45 +0800 Subject: [PATCH 05/20] Fix: Refactored the SystemMediaControlResolver The SystemMediaControlResolver has been refactored to better handle various Android versions and OEM customizations. It also introduces dedicated checks for One UI versions and displays a Toast message when media output settings cannot be opened. Specific changes include: * The logic for launching the system media control dialog has been refactored into a new private function `startSystemMediaControl()`. * Added a new function `getOneUIVersionReadable()` to retrieve the One UI version. * Fall back to `startSystemMediaControl()` when launching OEM-specific media control dialogs fails. * Display a Toast message when media output settings cannot be opened. * Added new strings in `strings.xml`. --- .../oem/SystemMediaControlResolver.kt | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index 845782b3f6..cf44316e66 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -66,12 +66,22 @@ class SystemMediaControlResolver(val context: Context) { if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } - } else { - // zh: Android 11 及以下 + } else if (Build.VERSION.SDK_INT == 30) { + // Android 11 val tag = startNativeMediaDialogForAndroid11(context) if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } + } else { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + val tag = startNativeMediaDialog(intent) + if (!tag) { + Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() + } } } @@ -127,9 +137,12 @@ class SystemMediaControlResolver(val context: Context) { * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property */ - private fun getOneUIVersionReadable(): String? { - return try {1 - val value = getSystemProperties("ro.build.version.oneui") + @SuppressLint("PrivateApi") + fun getOneUIVersionReadable(): String? { + return try { + val systemProperties = Class.forName("android.os.SystemProperties") + val get = systemProperties.getMethod("get", String::class.java) + val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() if (value.isEmpty()) return null val code = value.toIntOrNull() ?: return null val major = code / 10000 @@ -140,17 +153,4 @@ class SystemMediaControlResolver(val context: Context) { null } } - - @SuppressLint("PrivateApi") - private fun getSystemProperties(key: String): String { - val ret: String = try { - Class.forName("android.os.SystemProperties").getDeclaredMethod("get", String::class.java).invoke(null, key) as String - } catch (iAE: IllegalArgumentException) { - throw iAE - } catch (e: Exception) { - "" - } - return ret - } - } \ No newline at end of file From fba5958f182fd355465afcb1a6098961af452461 Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Sun, 17 Aug 2025 10:39:11 +0800 Subject: [PATCH 06/20] Remove One UI media activity calls Temporarily disable One UI MediaActivity calls and wait until it is certain that they will not be deleted before adding them back. Signed-off-by: ghhccghk <2137610394@qq.com> --- .../oem/SystemMediaControlResolver.kt | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index cf44316e66..304c9cfaae 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -29,18 +29,19 @@ class SystemMediaControlResolver(val context: Context) { startSystemMediaControl() } } - (getOneUIVersionReadable() != null) -> { - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - setClassName( - "com.samsung.android.mdx.quickboard", - "com.samsung.android.mdx.quickboard.view.MediaActivity" - ) - } - if (!startIntent(intent)) { - startSystemMediaControl() - } - } + //zh:临时禁用OneUI MediaActivity调用,等待未来确定不会被删除再添加回去 +// (getOneUIVersionReadable() != null) -> { +// val intent = Intent().apply { +// flags = Intent.FLAG_ACTIVITY_NEW_TASK +// setClassName( +// "com.samsung.android.mdx.quickboard", +// "com.samsung.android.mdx.quickboard.view.MediaActivity" +// ) +// } +// if (!startIntent(intent)) { +// startSystemMediaControl() +// } +// } else -> { startSystemMediaControl() @@ -133,24 +134,24 @@ class SystemMediaControlResolver(val context: Context) { } } - /** - * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null - * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property - */ - @SuppressLint("PrivateApi") - fun getOneUIVersionReadable(): String? { - return try { - val systemProperties = Class.forName("android.os.SystemProperties") - val get = systemProperties.getMethod("get", String::class.java) - val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() - if (value.isEmpty()) return null - val code = value.toIntOrNull() ?: return null - val major = code / 10000 - val minor = (code / 100) % 100 - val patch = code % 100 - "$major.$minor.$patch" - } catch (e: Exception) { - null - } - } +// /** +// * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null +// * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property +// */ +// @SuppressLint("PrivateApi") +// private fun getOneUIVersionReadable(): String? { +// return try { +// val systemProperties = Class.forName("android.os.SystemProperties") +// val get = systemProperties.getMethod("get", String::class.java) +// val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() +// if (value.isEmpty()) return null +// val code = value.toIntOrNull() ?: return null +// val major = code / 10000 +// val minor = (code / 100) % 100 +// val patch = code % 100 +// "$major.$minor.$patch" +// } catch (e: Exception) { +// null +// } +// } } \ No newline at end of file From d007e98ffde63974c6a424245e6f3637bcafd49e Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Mon, 22 Sep 2025 21:39:32 +0800 Subject: [PATCH 07/20] feat: Optimized media output panel availability checks Adjusted media output panel availability check logic based on Android version. - For Android 14 and above, assume the panel is always available. - For Android 12 and 13, check if the system UI can handle the media output dialog intent. - For Android 11 and below, check if the Settings app can handle the media output panel intent. If the panel is unavailable, hide the media control buttons. Signed-off-by: ghhccghk <2137610394@qq.com> --- .../ui/components/FullBottomSheet.kt | 64 ++++++++++++++++++- app/src/main/res/values/strings.xml | 3 + 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 08d32cafb7..9959a9480f 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -4,7 +4,10 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context +import android.content.Intent import android.content.SharedPreferences +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.drawable.BitmapDrawable @@ -416,10 +419,16 @@ class FullBottomSheet bottomSheetFavoriteButton.addOnCheckedChangeListener(this) - bottomSheetMediaControl.setOnClickListener { - SystemMediaControlResolver(context).intentSystemMediaDialog() + if (isMediaOutputPanelSupported(context)){ + bottomSheetMediaControl.setOnClickListener { + SystemMediaControlResolver(context).intentSystemMediaDialog() + } + } else { + bottomSheetMediaControl.visibility = GONE } + + bottomSheetPlaylistButton.setOnClickListener { ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) if (instance != null) @@ -1339,4 +1348,55 @@ class FullBottomSheet } } + fun isMediaOutputPanelSupported(context: Context): Boolean { + return when { + Build.VERSION.SDK_INT >= 34 -> { + // Android 14+ 系统一定支持 + true + } + Build.VERSION.SDK_INT >= 31 -> { + // Android 12~13 + val intent = Intent().apply { + action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" + setPackage("com.android.systemui") + putExtra("package_name", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + Build.VERSION.SDK_INT == 30 -> { + // Android 11 + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + else -> { + // Android 10 及以下 + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + } + } + + private fun isSystemIntentAvailable(context: Context, intent: Intent): Boolean { + val resolveInfoList = context.packageManager.queryIntentActivities(intent, 0) + for (resolveInfo in resolveInfoList) { + val activityInfo = resolveInfo.activityInfo + val applicationInfo: ApplicationInfo? = activityInfo?.applicationInfo + if (applicationInfo != null && (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + return true + } + } + return false + } + + + + } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7953a6ea62..b419494d32 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -472,6 +472,9 @@ Grid (compact) Album year year]]> + System Media Controller + Unable to open media output settings + Invalid character in the name Translators ReplayGain From 4a684a06ee44f8f54879f23ae7573047453d9130 Mon Sep 17 00:00:00 2001 From: kleidis Date: Wed, 5 Nov 2025 11:02:12 +0100 Subject: [PATCH 08/20] feat: Add playback speed control Adds a new button and dialog to the player which allows you to set a custom playback speed --- .../ui/components/FullBottomSheet.kt | 6 --- .../res/layout-w600dp-land/full_player.xml | 38 +++++++++++++++++++ app/src/main/res/layout/full_player.xml | 20 ++++++++++ app/src/main/res/values/strings.xml | 2 + 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 9959a9480f..f249f47153 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -411,12 +411,6 @@ class FullBottomSheet } } - bottomSheetPlaybackSpeedButton.setOnClickListener { - ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) - if (instance != null) - showPlaybackSpeedDialog() - } - bottomSheetFavoriteButton.addOnCheckedChangeListener(this) if (isMediaOutputPanelSupported(context)){ diff --git a/app/src/main/res/layout-w600dp-land/full_player.xml b/app/src/main/res/layout-w600dp-land/full_player.xml index 97a7c23830..697aa84f26 100644 --- a/app/src/main/res/layout-w600dp-land/full_player.xml +++ b/app/src/main/res/layout-w600dp-land/full_player.xml @@ -422,6 +422,44 @@ app:toggleCheckedStateOnClick="false" app:tooltipText="@string/timer" /> + + + + + + %s: %.2fx Lock tempo and pitch Reset + Playback speed + Reset Current playlist Failed to resume music playback Click here to open the app and continue playback From 8f3d7a823baff4143309da3e18313f4f3767fa65 Mon Sep 17 00:00:00 2001 From: kleidis Date: Thu, 6 Nov 2025 13:15:39 +0100 Subject: [PATCH 09/20] Move playback speed button where requested --- .../res/layout-w600dp-land/full_player.xml | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/app/src/main/res/layout-w600dp-land/full_player.xml b/app/src/main/res/layout-w600dp-land/full_player.xml index 697aa84f26..5e2c32634b 100644 --- a/app/src/main/res/layout-w600dp-land/full_player.xml +++ b/app/src/main/res/layout-w600dp-land/full_player.xml @@ -422,26 +422,6 @@ app:toggleCheckedStateOnClick="false" app:tooltipText="@string/timer" /> - - Date: Fri, 8 Aug 2025 21:41:27 +0800 Subject: [PATCH 10/20] =?UTF-8?q?Feat:=20Add=20media=20output=20switcher?= =?UTF-8?q?=20support=20for=20original=20equipment=20manufacturers=20(OEMs?= =?UTF-8?q?)=20Code=20sources:=20https://github.com/Moriafly/media-kit=20a?= =?UTF-8?q?nd=20https://github.com/Yos-X/FlamingoSank=20The=20functionalit?= =?UTF-8?q?y=20works=20as=20follows=20across=20different=20Android=20versi?= =?UTF-8?q?ons=20and=20OEM=20ROMs:=20-=20For=20Android=2014=20and=20higher?= =?UTF-8?q?,=20it=20uses=20MediaRouter2#showSystemOutputSwitcher.=20-=20Fo?= =?UTF-8?q?r=20Android=2012=20and=20higher,=20it=20uses=20the=20=E2=80=9Cc?= =?UTF-8?q?om.android.systemui.action.LAUNCH=5FMEDIA=5FOUTPUT=5FDIALOG?= =?UTF-8?q?=E2=80=9D=20action.=20-=20For=20Android=2011,=20it=20uses=20the?= =?UTF-8?q?=20=E2=80=9Ccom.android.settings.panel.action.MEDIA=5FOUTPUT?= =?UTF-8?q?=E2=80=9D=20action.=20-=20For=20MIUI=20(Xiaomi),=20it=20uses=20?= =?UTF-8?q?=E2=80=9Cmiui.systemui.miplay.MiPlayDetailActivity=E2=80=9D.=20?= =?UTF-8?q?-=20For=20One=20UI=20(Samsung),=20it=20uses=20=E2=80=9Ccom.sams?= =?UTF-8?q?ung.android.mdx.quickboard.view.MediaActivity=E2=80=9D.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gramophone/ui/components/FullBottomSheet.kt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index f249f47153..3de09ee9c6 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -43,7 +43,7 @@ import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player import androidx.media3.common.util.Log -import androidx.media3.session.MediaBrowser +import androidx.media3.session.MediaController import androidx.media3.session.SessionError import androidx.media3.session.SessionResult import androidx.preference.PreferenceManager @@ -219,6 +219,8 @@ class FullBottomSheet private var colorSecondaryContainerFinalColor: Int = -1 private var colorOnSecondaryContainerFinalColor: Int = -1 private var colorContrastFaintedFinalColor: Int = -1 + private var playlistNowPlaying: TextView? = null + private var playlistNowPlayingCover: ImageView? = null private var lastDisposable: Disposable? = null init { @@ -413,16 +415,6 @@ class FullBottomSheet bottomSheetFavoriteButton.addOnCheckedChangeListener(this) - if (isMediaOutputPanelSupported(context)){ - bottomSheetMediaControl.setOnClickListener { - SystemMediaControlResolver(context).intentSystemMediaDialog() - } - } else { - bottomSheetMediaControl.visibility = GONE - } - - - bottomSheetPlaylistButton.setOnClickListener { ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) if (instance != null) @@ -1088,8 +1080,6 @@ class FullBottomSheet bottomSheetTimerButton.iconTint = ColorStateList.valueOf(colorOnSurface) - bottomSheetPlaybackSpeedButton.iconTint = - ColorStateList.valueOf(colorOnSurface) bottomSheetPlaylistButton.iconTint = ColorStateList.valueOf(colorOnSurface) bottomSheetShuffleButton.iconTint = From 97da03b4b4b318e3198ddbbf8678dbad597c1f84 Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Sat, 16 Aug 2025 15:06:07 +0800 Subject: [PATCH 11/20] Modify the position of the media control buttons in the full_player.xml layout. The media control buttons have been removed from the linear layout at the bottom of the screen and moved to the top of the screen. This button is now fixed at the top of the parent layout and has some margin for adjustment. --- app/src/main/res/layout/full_player.xml | 59 +++++++++++++++++-------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/app/src/main/res/layout/full_player.xml b/app/src/main/res/layout/full_player.xml index 0ba214ddc2..d4448af2ae 100644 --- a/app/src/main/res/layout/full_player.xml +++ b/app/src/main/res/layout/full_player.xml @@ -29,25 +29,46 @@ app:layout_constraintVertical_bias="0.0" app:tooltipText="@string/expand_less" /> - + + + Date: Mon, 22 Sep 2025 21:39:32 +0800 Subject: [PATCH 12/20] feat: Optimized media output panel availability checks Adjusted media output panel availability check logic based on Android version. - For Android 14 and above, assume the panel is always available. - For Android 12 and 13, check if the system UI can handle the media output dialog intent. - For Android 11 and below, check if the Settings app can handle the media output panel intent. If the panel is unavailable, hide the media control buttons. Signed-off-by: ghhccghk <2137610394@qq.com> --- .../gramophone/ui/components/FullBottomSheet.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 3de09ee9c6..210aa3bb52 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -415,6 +415,16 @@ class FullBottomSheet bottomSheetFavoriteButton.addOnCheckedChangeListener(this) + if (isMediaOutputPanelSupported(context)){ + bottomSheetMediaControl.setOnClickListener { + SystemMediaControlResolver(context).intentSystemMediaDialog() + } + } else { + bottomSheetMediaControl.visibility = GONE + } + + + bottomSheetPlaylistButton.setOnClickListener { ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) if (instance != null) From 3f632591dce6d2dedbf4e0b7fbfcec68358986cf Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Fri, 19 Dec 2025 20:17:18 +0800 Subject: [PATCH 13/20] I addressed the differences between the media control buttons and the main code. Signed-off-by: ghhccghk <2137610394@qq.com> --- .../ui/components/FullBottomSheet.kt | 5 +++ app/src/main/res/layout/full_player.xml | 41 ------------------- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 210aa3bb52..68aa8f15b0 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -106,6 +106,11 @@ import uk.akane.libphonograph.items.albumId import uk.akane.libphonograph.items.artistId import uk.akane.libphonograph.manipulator.ItemManipulator import kotlin.math.min +import androidx.core.content.edit +import androidx.core.widget.NestedScrollView +import androidx.media3.common.PlaybackParameters +import androidx.media3.session.MediaBrowser +import com.google.android.material.checkbox.MaterialCheckBox @SuppressLint("NotifyDataSetChanged") class FullBottomSheet diff --git a/app/src/main/res/layout/full_player.xml b/app/src/main/res/layout/full_player.xml index d4448af2ae..ea65dba58d 100644 --- a/app/src/main/res/layout/full_player.xml +++ b/app/src/main/res/layout/full_player.xml @@ -29,27 +29,6 @@ app:layout_constraintVertical_bias="0.0" app:tooltipText="@string/expand_less" /> - - - - Date: Fri, 19 Dec 2025 20:32:28 +0800 Subject: [PATCH 14/20] fix: Addressing issues caused by variable base code Signed-off-by: ghhccghk <2137610394@qq.com> --- .../gramophone/ui/components/FullBottomSheet.kt | 14 +++++++++----- app/src/main/res/values/strings.xml | 2 -- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 68aa8f15b0..e51f29843e 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -43,7 +43,7 @@ import androidx.media3.common.MediaMetadata import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player import androidx.media3.common.util.Log -import androidx.media3.session.MediaController +import androidx.media3.session.MediaBrowser import androidx.media3.session.SessionError import androidx.media3.session.SessionResult import androidx.preference.PreferenceManager @@ -96,7 +96,6 @@ import org.akanework.gramophone.logic.utils.AudioFormatDetector.SpatialFormat import org.akanework.gramophone.logic.utils.CalculationUtils import org.akanework.gramophone.logic.utils.ColorUtils import org.akanework.gramophone.logic.utils.Flags -import org.akanework.gramophone.logic.utils.convertDurationToTimeStamp import org.akanework.gramophone.logic.utils.exoplayer.oem.SystemMediaControlResolver import org.akanework.gramophone.ui.MainActivity import org.akanework.gramophone.ui.fragments.ArtistSubFragment @@ -109,7 +108,6 @@ import kotlin.math.min import androidx.core.content.edit import androidx.core.widget.NestedScrollView import androidx.media3.common.PlaybackParameters -import androidx.media3.session.MediaBrowser import com.google.android.material.checkbox.MaterialCheckBox @SuppressLint("NotifyDataSetChanged") @@ -224,8 +222,6 @@ class FullBottomSheet private var colorSecondaryContainerFinalColor: Int = -1 private var colorOnSecondaryContainerFinalColor: Int = -1 private var colorContrastFaintedFinalColor: Int = -1 - private var playlistNowPlaying: TextView? = null - private var playlistNowPlayingCover: ImageView? = null private var lastDisposable: Disposable? = null init { @@ -418,6 +414,12 @@ class FullBottomSheet } } + bottomSheetPlaybackSpeedButton.setOnClickListener { + ViewCompat.performHapticFeedback(it, HapticFeedbackConstantsCompat.CONTEXT_CLICK) + if (instance != null) + showPlaybackSpeedDialog() + } + bottomSheetFavoriteButton.addOnCheckedChangeListener(this) if (isMediaOutputPanelSupported(context)){ @@ -1095,6 +1097,8 @@ class FullBottomSheet bottomSheetTimerButton.iconTint = ColorStateList.valueOf(colorOnSurface) + bottomSheetPlaybackSpeedButton.iconTint = + ColorStateList.valueOf(colorOnSurface) bottomSheetPlaylistButton.iconTint = ColorStateList.valueOf(colorOnSurface) bottomSheetShuffleButton.iconTint = diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b71be02ad..b419494d32 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -182,8 +182,6 @@ %s: %.2fx Lock tempo and pitch Reset - Playback speed - Reset Current playlist Failed to resume music playback Click here to open the app and continue playback From a2689b57c70003989c7bc54967d9f519950ac686 Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Tue, 30 Dec 2025 19:28:21 +0800 Subject: [PATCH 15/20] =?UTF-8?q?fix=EF=BC=9AMove=20the=20media=20control?= =?UTF-8?q?=20buttons=20to=20the=20top=20bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ghhccghk <2137610394@qq.com> --- .../res/layout-w600dp-land/full_player.xml | 34 +++++++++++ app/src/main/res/layout/full_player.xml | 56 +++++++------------ 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/app/src/main/res/layout-w600dp-land/full_player.xml b/app/src/main/res/layout-w600dp-land/full_player.xml index 5e2c32634b..5990c25030 100644 --- a/app/src/main/res/layout-w600dp-land/full_player.xml +++ b/app/src/main/res/layout-w600dp-land/full_player.xml @@ -46,6 +46,40 @@ app:layout_constraintVertical_bias="0.0" app:tooltipText="@string/playback_speed" /> + + + + + + - - - - Date: Tue, 30 Dec 2025 19:46:11 +0800 Subject: [PATCH 16/20] feat: Converted media control icon to vector graphics Removed the PNG version of `ic_media_control.png` and replaced it with a vector (XML) version. This helps reduce app size and improves scaling quality across different screen densities. Signed-off-by: ghhccghk <2137610394@qq.com> --- app/src/main/res/drawable/ic_media_control.png | Bin 1203 -> 0 bytes app/src/main/res/drawable/ic_media_control.xml | 13 +++++++++++++ 2 files changed, 13 insertions(+) delete mode 100644 app/src/main/res/drawable/ic_media_control.png create mode 100644 app/src/main/res/drawable/ic_media_control.xml diff --git a/app/src/main/res/drawable/ic_media_control.png b/app/src/main/res/drawable/ic_media_control.png deleted file mode 100644 index 0cc2b20ca622fb6b90dfb6041476f4c9748119d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmV;k1WfyhP)y000DfNklteDhy>=UmAVl$L0q_U0tqCLKmrLQ zkU$t?NJD{f%rMf-0>bmq}wpCoRljq?iD_f={sJ z2q7ahLpUO+pKv8sz$|HLrsEtwy{weZb9}=MGi{$niud+iCjXIp^AAJTJQ>yTGd6pbHaVl^{q-I4+Ycb=D2& zDp}vhC2bX#K;JpnDs+cSn5!iHI9d0vQcWCZSHtY!J}P;6MxJ{@^GYH~D8~~#K{=9C z`p`Vnw5uiVY>?cl#ahzQD3TQkMY47+qU}PFtSI&(nb)%8Yb~O$jh7ug z7jm1mh&CH9 zW4<}G_0ci7@?ya~V>Is@yI7!HqdyxXx@7RG24m9YmwjVIe;K{}ay9|%9q1clMBf^| zPIR`YcHj`el(-p-0W*v_0Kd^??1GlHd(1T+p7IP|c?%?vKmrLQkU)GZe*tOxbJNj) Rm6QMg002ovPDHLkV1m6&Egt{? diff --git a/app/src/main/res/drawable/ic_media_control.xml b/app/src/main/res/drawable/ic_media_control.xml new file mode 100644 index 0000000000..4bb2560ee6 --- /dev/null +++ b/app/src/main/res/drawable/ic_media_control.xml @@ -0,0 +1,13 @@ + + + + + + From 7f2766a8b86d1aad1e39751b0da5aa5a76f6fc5f Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Sat, 3 Jan 2026 16:36:13 +0800 Subject: [PATCH 17/20] Optimize code by removing redundant logic Signed-off-by: ghhccghk <2137610394@qq.com> --- .../utils/exoplayer/oem/MiPlayAudioSupport.kt | 11 +- .../oem/SystemMediaControlResolver.kt | 149 +++++++++++------- .../exoplayer/oem/UnstableMediaKitApi.kt | 26 --- .../ui/components/FullBottomSheet.kt | 53 +------ 4 files changed, 101 insertions(+), 138 deletions(-) delete mode 100644 app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt index 9a13cfb638..e9296c51a1 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/MiPlayAudioSupport.kt @@ -27,9 +27,8 @@ import android.content.Intent import android.content.pm.PackageManager /** - * 小米妙播适配 + * MiPlay Audio Support */ -@UnstableMediaKitApi object MiPlayAudioSupport { private const val ACTION_MIPLAY_DETAIL = "miui.intent.action.ACTIVITY_MIPLAY_DETAIL" private const val AUDIO_RECORD_CLASS = "miui.media.MiuiAudioPlaybackRecorder" @@ -38,7 +37,7 @@ object MiPlayAudioSupport { private const val WHITE_TARGET = "com.milink.service:hide_foreground" /** - * 查询是否支持妙播服务 + * Check if MiaoBo service is supported * * https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1944 */ @@ -62,7 +61,7 @@ object MiPlayAudioSupport { } /** - * 是否为国际版 + * Is it the international version */ private fun isInternationalBuild(): Boolean = try { @@ -75,7 +74,7 @@ object MiPlayAudioSupport { } /** - * 检查 SystemUI 是否包含处理妙播意图的 Activity + * Check whether SystemUI contains an Activity that handles the Miaobo intent. */ private fun systemUIReady(context: Context): Boolean { val intent = @@ -92,7 +91,7 @@ object MiPlayAudioSupport { } /** - * 检查妙播服务是否在 SystemUI 的前台服务通知白名单中 + * Check whether the Miaobo service is included in the SystemUI foreground service notification whitelist. */ private fun notificationReady(context: Context): Boolean = try { diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt index 304c9cfaae..fea9f2fa74 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/SystemMediaControlResolver.kt @@ -12,9 +12,8 @@ import androidx.annotation.RequiresApi import org.akanework.gramophone.R import org.akanework.gramophone.logic.utils.exoplayer.oem.MiPlayAudioSupport.supportMiPlay -class SystemMediaControlResolver(val context: Context) { - @OptIn(UnstableMediaKitApi::class) - fun intentSystemMediaDialog() { +object SystemMediaControlResolver { + fun intentSystemMediaDialog(context: Context) { // val manufacturer = Build.MANUFACTURER.lowercase() when { supportMiPlay(context) -> { @@ -25,68 +24,60 @@ class SystemMediaControlResolver(val context: Context) { "miui.systemui.miplay.MiPlayDetailActivity" ) } - if (!startIntent(intent)) { - startSystemMediaControl() + if (!startIntent(intent,context = context,)) { + startSystemMediaControl(context = context) + } + } + (getOneUIVersionReadable() != null) -> { + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + setClassName( + "com.samsung.android.mdx.quickboard", + "com.samsung.android.mdx.quickboard.view.MediaActivity" + ) + } + if (!startIntent(intent,context = context)) { + startSystemMediaControl(context = context,) } } - //zh:临时禁用OneUI MediaActivity调用,等待未来确定不会被删除再添加回去 -// (getOneUIVersionReadable() != null) -> { -// val intent = Intent().apply { -// flags = Intent.FLAG_ACTIVITY_NEW_TASK -// setClassName( -// "com.samsung.android.mdx.quickboard", -// "com.samsung.android.mdx.quickboard.view.MediaActivity" -// ) -// } -// if (!startIntent(intent)) { -// startSystemMediaControl() -// } -// } else -> { - startSystemMediaControl() + startSystemMediaControl(context = context,) } } } - private fun startSystemMediaControl(){ + private fun startSystemMediaControl(context: Context){ if (Build.VERSION.SDK_INT >= 34) { // zh: Android 14 及以上 + // en:Android 14 and above val tag = startNativeMediaDialogForAndroid14(context) if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } } else if (Build.VERSION.SDK_INT >= 31) { // zh: Android 12 及以上 + // en: Android 14 and above val intent = Intent().apply { action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" setPackage("com.android.systemui") putExtra("package_name", context.packageName) } - val tag = startNativeMediaDialog(intent) + val tag = startNativeMediaDialog(context = context,intent) if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } - } else if (Build.VERSION.SDK_INT == 30) { - // Android 11 + } else{ + // zh: Android 11 及以下 + // en: Android 11 and below val tag = startNativeMediaDialogForAndroid11(context) if (!tag) { Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() } - } else { - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = "com.android.settings.panel.action.MEDIA_OUTPUT" - putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) - } - val tag = startNativeMediaDialog(intent) - if (!tag) { - Toast.makeText(context, R.string.media_control_text_error, Toast.LENGTH_SHORT).show() - } } } - private fun startNativeMediaDialog(intent: Intent): Boolean { + private fun startNativeMediaDialog(context: Context,intent: Intent): Boolean { val resolveInfoList: List = context.packageManager.queryIntentActivities(intent, 0) for (resolveInfo in resolveInfoList) { @@ -125,7 +116,7 @@ class SystemMediaControlResolver(val context: Context) { return mediaRouter2.showSystemOutputSwitcher() } - private fun startIntent(intent: Intent): Boolean { + private fun startIntent(intent: Intent,context: Context): Boolean { return try { context.startActivity(intent) true @@ -134,24 +125,72 @@ class SystemMediaControlResolver(val context: Context) { } } -// /** -// * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null -// * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property -// */ -// @SuppressLint("PrivateApi") -// private fun getOneUIVersionReadable(): String? { -// return try { -// val systemProperties = Class.forName("android.os.SystemProperties") -// val get = systemProperties.getMethod("get", String::class.java) -// val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() -// if (value.isEmpty()) return null -// val code = value.toIntOrNull() ?: return null -// val major = code / 10000 -// val minor = (code / 100) % 100 -// val patch = code % 100 -// "$major.$minor.$patch" -// } catch (e: Exception) { -// null -// } -// } + /** + * zh: 获取 One UI 版本字符串(如 6.0.0),非三星或无此属性则返回 null + * en: Get One UI version string (e.g. 6.0.0), return null if not Samsung or no such property + */ + @SuppressLint("PrivateApi") + private fun getOneUIVersionReadable(): String? { + return try { + val systemProperties = Class.forName("android.os.SystemProperties") + val get = systemProperties.getMethod("get", String::class.java) + val value = (get.invoke(null, "ro.build.version.oneui") as String).trim() + if (value.isEmpty()) return null + val code = value.toIntOrNull() ?: return null + val major = code / 10000 + val minor = (code / 100) % 100 + val patch = code % 100 + "$major.$minor.$patch" + } catch (e: Exception) { + null + } + } + + fun isMediaOutputPanelSupported(context: Context): Boolean { + return when { + Build.VERSION.SDK_INT >= 34 -> { + // Android 14+ is support + true + } + Build.VERSION.SDK_INT >= 31 -> { + // Android 12~13 + val intent = Intent().apply { + action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" + setPackage("com.android.systemui") + putExtra("package_name", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + Build.VERSION.SDK_INT == 30 -> { + // Android 11 + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + else -> { + // Android 10 and below + val intent = Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = "com.android.settings.panel.action.MEDIA_OUTPUT" + putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) + } + isSystemIntentAvailable(context, intent) + } + } + } + + private fun isSystemIntentAvailable(context: Context, intent: Intent): Boolean { + val resolveInfoList = context.packageManager.queryIntentActivities(intent, 0) + for (resolveInfo in resolveInfoList) { + val activityInfo = resolveInfo.activityInfo + val applicationInfo: ApplicationInfo? = activityInfo?.applicationInfo + if (applicationInfo != null && (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { + return true + } + } + return false + } } \ No newline at end of file diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt deleted file mode 100644 index 53628ed9dd..0000000000 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/oem/UnstableMediaKitApi.kt +++ /dev/null @@ -1,26 +0,0 @@ -@file:Suppress("unused") - -package org.akanework.gramophone.logic.utils.exoplayer.oem - -/** - * Media Kit - * Copyright (C) 2025 Moriafly - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ - -@RequiresOptIn( - message = "This Media Kit API is experimental and is likely to change or be removed in the " + - "future", - level = RequiresOptIn.Level.ERROR, -) -@Retention(AnnotationRetention.BINARY) -annotation class UnstableMediaKitApi \ No newline at end of file diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index e51f29843e..7564ae9c6e 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -422,9 +422,9 @@ class FullBottomSheet bottomSheetFavoriteButton.addOnCheckedChangeListener(this) - if (isMediaOutputPanelSupported(context)){ + if (SystemMediaControlResolver.isMediaOutputPanelSupported(context)){ bottomSheetMediaControl.setOnClickListener { - SystemMediaControlResolver(context).intentSystemMediaDialog() + SystemMediaControlResolver.intentSystemMediaDialog(context) } } else { bottomSheetMediaControl.visibility = GONE @@ -1351,55 +1351,6 @@ class FullBottomSheet } } - fun isMediaOutputPanelSupported(context: Context): Boolean { - return when { - Build.VERSION.SDK_INT >= 34 -> { - // Android 14+ 系统一定支持 - true - } - Build.VERSION.SDK_INT >= 31 -> { - // Android 12~13 - val intent = Intent().apply { - action = "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" - setPackage("com.android.systemui") - putExtra("package_name", context.packageName) - } - isSystemIntentAvailable(context, intent) - } - Build.VERSION.SDK_INT == 30 -> { - // Android 11 - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = "com.android.settings.panel.action.MEDIA_OUTPUT" - putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) - } - isSystemIntentAvailable(context, intent) - } - else -> { - // Android 10 及以下 - val intent = Intent().apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - action = "com.android.settings.panel.action.MEDIA_OUTPUT" - putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.packageName) - } - isSystemIntentAvailable(context, intent) - } - } - } - - private fun isSystemIntentAvailable(context: Context, intent: Intent): Boolean { - val resolveInfoList = context.packageManager.queryIntentActivities(intent, 0) - for (resolveInfo in resolveInfoList) { - val activityInfo = resolveInfo.activityInfo - val applicationInfo: ApplicationInfo? = activityInfo?.applicationInfo - if (applicationInfo != null && (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0) { - return true - } - } - return false - } - - } From abc6b0c772bfe8a00362abfedbea5a068413ac75 Mon Sep 17 00:00:00 2001 From: ghhccghk <2137610394@qq.com> Date: Tue, 17 Feb 2026 15:51:57 +0800 Subject: [PATCH 18/20] feat(UI): Refactor full player layout and build configurations - In `build.gradle.kts`, set the debug signing configuration for the release build type. - In `full_player.xml`, reorder the media control and playback speed buttons. - In `layout-w600dp-land/full_player.xml`, clean up redundant layout attributes for the album cover card view. Signed-off-by: ghhccghk <2137610394@qq.com> --- app/build.gradle.kts | 1 + .../res/layout-w600dp-land/full_player.xml | 16 -------- app/src/main/res/layout/full_player.xml | 40 +++++++++---------- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b182667dec..4ee1485d17 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -178,6 +178,7 @@ android { isJniDebuggable = true isPseudoLocalesEnabled = true matchingFallbacks += "release" + signingConfig = signingConfigs.getByName("debug") } debug { isPseudoLocalesEnabled = true diff --git a/app/src/main/res/layout-w600dp-land/full_player.xml b/app/src/main/res/layout-w600dp-land/full_player.xml index 5990c25030..fe19c639a2 100644 --- a/app/src/main/res/layout-w600dp-land/full_player.xml +++ b/app/src/main/res/layout-w600dp-land/full_player.xml @@ -64,22 +64,6 @@ app:layout_constraintTop_toBottomOf="@id/playback_speed" app:layout_constraintVertical_bias="0.0" /> - - + + - - Date: Tue, 17 Feb 2026 16:06:30 +0800 Subject: [PATCH 19/20] fix merger error Signed-off-by: ghhccghk <2137610394@qq.com> --- .../ui/components/FullBottomSheet.kt | 11 +----- .../res/layout-w600dp-land/full_player.xml | 20 +---------- app/src/main/res/layout/full_player.xml | 36 +++++++++---------- 3 files changed, 20 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt index 7564ae9c6e..e3bcaf982d 100644 --- a/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt +++ b/app/src/main/java/org/akanework/gramophone/ui/components/FullBottomSheet.kt @@ -4,10 +4,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context -import android.content.Intent import android.content.SharedPreferences -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.drawable.BitmapDrawable @@ -105,10 +102,6 @@ import uk.akane.libphonograph.items.albumId import uk.akane.libphonograph.items.artistId import uk.akane.libphonograph.manipulator.ItemManipulator import kotlin.math.min -import androidx.core.content.edit -import androidx.core.widget.NestedScrollView -import androidx.media3.common.PlaybackParameters -import com.google.android.material.checkbox.MaterialCheckBox @SuppressLint("NotifyDataSetChanged") class FullBottomSheet @@ -1351,6 +1344,4 @@ class FullBottomSheet } } - - -} +} \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp-land/full_player.xml b/app/src/main/res/layout-w600dp-land/full_player.xml index fe19c639a2..d09626e4bc 100644 --- a/app/src/main/res/layout-w600dp-land/full_player.xml +++ b/app/src/main/res/layout-w600dp-land/full_player.xml @@ -440,24 +440,6 @@ app:toggleCheckedStateOnClick="false" app:tooltipText="@string/timer" /> - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/full_player.xml b/app/src/main/res/layout/full_player.xml index 0da4402020..ab63758ebd 100644 --- a/app/src/main/res/layout/full_player.xml +++ b/app/src/main/res/layout/full_player.xml @@ -449,24 +449,24 @@ app:toggleCheckedStateOnClick="false" app:tooltipText="@string/timer" /> - - + + Date: Tue, 17 Feb 2026 16:09:47 +0800 Subject: [PATCH 20/20] remove userdebug signingConfig Signed-off-by: ghhccghk <2137610394@qq.com> --- app/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4ee1485d17..b182667dec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -178,7 +178,6 @@ android { isJniDebuggable = true isPseudoLocalesEnabled = true matchingFallbacks += "release" - signingConfig = signingConfigs.getByName("debug") } debug { isPseudoLocalesEnabled = true