Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dd177c5
Initial plan
Copilot May 12, 2026
c45d9cd
Add system stream controls and brightness panel updates
Copilot May 12, 2026
ba01126
Refine constants in system volume panel
Copilot May 12, 2026
2f877a9
Log DND permission failures in system panel
Copilot May 12, 2026
80217a5
Guard notification manager lookup in toggles
Copilot May 12, 2026
15fa497
Reuse slider value label component
Copilot May 12, 2026
6e1fb02
Reuse main-thread handler for brightness observer
Copilot May 12, 2026
967fd6d
Rename shared slider text helper
Copilot May 12, 2026
6856176
Document call modes and log brightness read failure
Copilot May 12, 2026
e049c4a
Address PR review feedback for system sliders panel
Copilot May 12, 2026
ce4fa96
Apply final review cleanup in system volume panel
Copilot May 12, 2026
d5e6d27
Use tolerance for brightness float comparison
Copilot May 12, 2026
07f6292
Extract brightness change tolerance constant
Copilot May 12, 2026
6e2e38e
Fix follow-up review issues for slider layout and brightness
Copilot May 12, 2026
f06ae0b
Document DisplayManagerProxy brightness API contract
Copilot May 12, 2026
65131cf
Apply final review nits for brightness slider and proxy docs
Copilot May 12, 2026
5d989da
Fix display brightness gamma conversion on Xiaomi devices
yume-chan May 12, 2026
d2cd0f6
Fix brightness proxy reflection and add auto-brightness controls
Copilot May 12, 2026
f4e50fa
Convert BrightnessUtils to a class
yume-chan May 13, 2026
e3b057c
Remove brightness controls while keeping system volume controls
Copilot May 17, 2026
87e7f00
Change action bar icon to settings
yume-chan May 17, 2026
b953389
Polish the system volume sliders
yume-chan May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ android {
applicationId = "moe.chensi.volume"
minSdk = 33
targetSdk = 35
versionCode = 19
versionName = "0.3-beta.15"
versionCode = 20
versionName = "0.3-beta.16"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
37 changes: 21 additions & 16 deletions app/src/main/java/moe/chensi/volume/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
Expand Down Expand Up @@ -60,6 +60,7 @@ import androidx.core.net.toUri
import moe.chensi.volume.compose.AboutDialog
import moe.chensi.volume.compose.AppVolumeList
import moe.chensi.volume.compose.CrashReportDialog
import moe.chensi.volume.compose.SystemVolumePanel
import moe.chensi.volume.compose.ToggleButton
import moe.chensi.volume.ui.theme.VolumeManagerTheme
import org.joor.Reflect
Expand Down Expand Up @@ -197,10 +198,10 @@ class MainActivity : ComponentActivity() {
if (manager.shizukuStatus == Manager.ShizukuStatus.Connected) {
ToggleButton(
checked = showAll,
checkedIcon = Icons.Default.Visibility,
checkedDescription = "Hide inactive or hidden apps",
uncheckedIcon = Icons.Default.VisibilityOff,
uncheckedDescription = "Show all apps"
checkedIcon = Icons.Default.Check,
checkedDescription = "Save",
uncheckedIcon = Icons.Default.Settings,
uncheckedDescription = "Settings"
) {
showAll = it
}
Expand Down Expand Up @@ -326,7 +327,20 @@ class MainActivity : ComponentActivity() {
apps = manager.apps.values,
showEmpty = true,
Comment thread
yume-chan marked this conversation as resolved.
showAll = showAll,
onShowAll = { showAll = true })
onShowAll = { showAll = true },
content = {
item("system_volume_panel_main") {
SystemVolumePanel(
audioManager = manager.audioManager,
notificationManagerProxy = manager.notificationManagerProxy,
showCallVolumeAlways = true,
applyVisibilityFilter = !showAll,
allowVisibilityConfig = showAll,
isSliderVisible = manager::isSystemSliderVisible,
onSliderVisibilityChange = manager::setSystemSliderVisible,
)
}
})
}
}
}
Expand Down Expand Up @@ -356,14 +370,11 @@ class MainActivity : ComponentActivity() {

@Composable
fun ServiceStatus() {
var permissionGranted by remember { mutableStateOf(false) }
var serviceEnabled by remember { mutableStateOf(false) }
var errorInfo by remember { mutableStateOf<ErrorInfo?>(null) }

LaunchedEffect(0) {
try {
grantSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
permissionGranted = true
} catch (e: Exception) {
Log.e(TAG, "Can't add WRITE_SECURE_SETTINGS permission", e)
errorInfo = ErrorInfo(e.message!!, e.stackTraceToString())
Expand All @@ -374,7 +385,6 @@ class MainActivity : ComponentActivity() {
enableAccessibilityService(
ComponentName(this@MainActivity, Service::class.java).flattenToString()
)
serviceEnabled = true
} catch (e: Exception) {
Log.e(TAG, "Can't enable accessibility service", e)
}
Expand Down Expand Up @@ -404,11 +414,6 @@ class MainActivity : ComponentActivity() {
})
}

Column {
Text(text = "Permission granted: ${if (permissionGranted) "Yes" else "No"}")
Text(text = "Service enabled: ${if (serviceEnabled) "Yes" else "No"}")
}

Log.i(TAG, "Manufacturer: ${Build.MANUFACTURER}")

if (!isIgnoringBatteryOptimization) {
Expand Down
21 changes: 21 additions & 0 deletions app/src/main/java/moe/chensi/volume/Manager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.datastore.preferences.core.Preferences
import moe.chensi.volume.data.App
import moe.chensi.volume.data.AppPreferencesStore
import moe.chensi.volume.system.AudioPlaybackConfigurationProxy
import moe.chensi.volume.system.NotificationManagerProxy
import moe.chensi.volume.system.PackageManagerProxy
import org.joor.Reflect
import rikka.shizuku.Shizuku
Expand Down Expand Up @@ -44,8 +45,25 @@ class Manager(context: Context, dataStore: DataStore<Preferences>) {
.apply { ToggleableBinderProxy.wrap(this) }
}
private val packageManager by lazy { PackageManagerProxy.get(context) }
val notificationManagerProxy = NotificationManagerProxy(context)

private val appPreferencesStore = AppPreferencesStore(dataStore)
private val _systemSliderVisibility = mutableStateMapOf<String, Boolean>()
val systemSliderVisibility: Map<String, Boolean>
get() = _systemSliderVisibility

fun isSystemSliderVisible(id: String): Boolean {
return _systemSliderVisibility[id] ?: true
}

fun setSystemSliderVisible(id: String, visible: Boolean) {
if ((_systemSliderVisibility[id] ?: true) == visible) {
return
}

_systemSliderVisibility[id] = visible
appPreferencesStore.setSystemSliderVisible(id, visible)
}

val apps = mutableStateMapOf<String, App>()

Expand Down Expand Up @@ -162,6 +180,9 @@ class Manager(context: Context, dataStore: DataStore<Preferences>) {
}
}

_systemSliderVisibility.clear()
_systemSliderVisibility.putAll(appPreferencesStore.systemSliderVisibility)

if (first) {
initialize()
}
Expand Down
45 changes: 16 additions & 29 deletions app/src/main/java/moe/chensi/volume/Service.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MusicNote
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
Expand All @@ -45,7 +43,7 @@ import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import moe.chensi.volume.compose.AppVolumeList
import moe.chensi.volume.compose.StreamVolumeSlider
import moe.chensi.volume.compose.SystemVolumePanel
import moe.chensi.volume.compose.VolumeChangeObserver
import moe.chensi.volume.system.ActivityTaskManagerProxy
import moe.chensi.volume.ui.theme.VolumeManagerTheme
Expand Down Expand Up @@ -160,8 +158,7 @@ class Service : AccessibilityService() {

Log.i(TAG, "onAttachedToWindow manufacturer: ${Build.MANUFACTURER}")

@Suppress("SpellCheckingInspection")
if (windowManager.isCrossWindowBlurEnabled && isHardwareAccelerated && Build.MANUFACTURER != "realme") {
@Suppress("SpellCheckingInspection") if (windowManager.isCrossWindowBlurEnabled && isHardwareAccelerated && Build.MANUFACTURER != "realme") {
background =
Reflect.on(rootSurfaceControl).call("createBackgroundBlurDrawable").apply {
call("setBlurRadius", 200)
Expand All @@ -188,37 +185,27 @@ class Service : AccessibilityService() {
override fun Content() {
return VolumeManagerTheme {
Surface(
color = Color.Transparent,
contentColor = Color.White,
color = Color(1f, 1f, 1f, 0.3f),
contentColor = MaterialTheme.colorScheme.onSurface,
shape = RoundedCornerShape(40f)
) {
Column(
modifier = Modifier
.background(
Color(1f, 1f, 1f, 0.3f), RoundedCornerShape(40f)
)
.padding(20.dp, 16.dp)
modifier = Modifier.padding(20.dp, 16.dp)
) {
AppVolumeList(
apps = manager.apps.values,
showAll = false,
onChange = this@Service.handler::startIdleTimer
) {
item(AudioManager.STREAM_MUSIC) {
StreamVolumeSlider(
AudioManager.STREAM_MUSIC,
Icons.Default.MusicNote,
"Music",
manager.audioManager,
onChange = this@Service.handler::startIdleTimer
)
}

item(AudioManager.STREAM_NOTIFICATION) {
StreamVolumeSlider(
AudioManager.STREAM_NOTIFICATION,
Icons.Default.Notifications,
"Notifications",
manager.audioManager,
item("system_volume_panel") {
SystemVolumePanel(
audioManager = manager.audioManager,
notificationManagerProxy = manager.notificationManagerProxy,
showCallVolumeAlways = false,
applyVisibilityFilter = true,
allowVisibilityConfig = false,
isSliderVisible = manager::isSystemSliderVisible,
onSliderVisibilityChange = manager::setSystemSliderVisible,
onChange = this@Service.handler::startIdleTimer
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ class ToggleableBinderProxy(private val base: IBinder) : IBinder by base {
): Boolean {
return (if (enabled) shizukuWrapper else base).transact(code, data, reply, flags)
}
}
}
5 changes: 3 additions & 2 deletions app/src/main/java/moe/chensi/volume/compose/AppVolumeList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
Expand Down Expand Up @@ -154,7 +153,9 @@ fun AppVolumeList(
} else if (showEmpty) {
item {
Column(
modifier = Modifier.fillParentMaxSize(),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 48.dp),
verticalArrangement = Arrangement.spacedBy(
12.dp, Alignment.CenterVertically
),
Expand Down
81 changes: 50 additions & 31 deletions app/src/main/java/moe/chensi/volume/compose/StreamVolumeSlider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.content.IntentFilter
import android.media.AudioManager
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ExperimentalMaterial3Api
Expand Down Expand Up @@ -80,6 +81,7 @@ fun StreamVolumeSlider(
icon: ImageVector,
name: String,
audioManager: AudioManager,
footer: (@Composable () -> Unit)? = null,
onChange: (() -> Unit)? = null
) {
val context = LocalContext.current
Expand All @@ -103,39 +105,56 @@ fun StreamVolumeSlider(
volume = audioManager.getStreamVolume(streamType)
}

TrackSlider(
cornerRadius = 20.dp,
value = volume.toFloat(),
valueRange = 0f..maxVolume,
onValueChange = { value ->
volume = value.toInt()
audioManager.setStreamVolume(streamType, value.toInt(), 0)
onChange?.invoke()
},
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(12.dp, 8.dp)
) {
Icon(
imageVector = icon,
contentDescription = name,
modifier = Modifier.size(32.dp),
)

Text(
text = name,
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
TrackSlider(
modifier = Modifier.weight(1f),
cornerRadius = 20.dp,
value = volume.toFloat(),
valueRange = 0f..maxVolume,
onValueChange = { value ->
val target = value.toInt()
if (volume == target) {
return@TrackSlider
}

Text(
text = "$volume/${maxVolume.toInt()}",
style = Typography.bodySmall,
maxLines = 1,
)
volume = target
audioManager.setStreamVolume(streamType, target, 0)
onChange?.invoke()
},
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp, 8.dp)
) {
Icon(
imageVector = icon,
contentDescription = name,
modifier = Modifier.size(32.dp),
)
StreamSliderTextContent(name = name, valueText = "$volume/${maxVolume.toInt()}")
}
}

footer?.invoke()
}
}

@Composable
internal fun RowScope.StreamSliderTextContent(name: String, valueText: String) {
Text(
text = name,
modifier = Modifier.weight(1f),
Comment thread
yume-chan marked this conversation as resolved.
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

Text(
text = valueText,
style = Typography.bodySmall,
maxLines = 1,
)
}
Loading