app/src/main/java/dev/equwal/assistkey/display/ExtraDim.kt (5451 bytes)
1 package dev.equwal.assistkey.display 2 3 import android.content.Context 4 import dev.equwal.assistkey.device.Device 5 import dev.equwal.assistkey.shell.Shell 6 7 /** 8 * Dimmer than the dimmest the system allows. 9 * 10 * Measured on the Viwoods reader: the frontlight is an ordinary backlight LED 11 * whose driver accepts 1..255 (and has 2047 real steps behind that), but the 12 * framework will not go below 5. Ask for 4 through any official route - the 13 * brightness setting, even the shell's `cmd display set-brightness` - and it 14 * snaps to zero. Writing the LED node directly gets under the floor, and the 15 * value then sticks until the system slider is next moved. 16 * 17 * The node belongs to `system`, so this needs more than the shell user: it 18 * works when Shizuku itself runs as root, or on firmware whose shell may `su` 19 * (userdebug builds, which is how these readers ship). Otherwise the write is 20 * refused and the screen says so. 21 */ 22 object ExtraDim { 23 24 private const val PREFS = "assistkey_display" 25 private const val K_LEVEL = "extra_dim" 26 27 /** The light nodes to try, in order. Detection looks for the same ones. */ 28 val nodes = listOf( 29 "/sys/class/leds/lcd-backlight/brightness", 30 "/sys/class/backlight/panel0-backlight/brightness" 31 ) 32 33 /** 0 is off; otherwise the raw value to hold the light at. */ 34 fun level(c: Context): Int = 35 c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getInt(K_LEVEL, 0) 36 37 /** The raw values on offer: everything under the system's floor. */ 38 fun levels(): List<Int> = (1 until (Device.brightnessFloor ?: 5)).toList().reversed() 39 40 fun set(c: Context, level: Int, done: (Boolean) -> Unit) { 41 c.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putInt(K_LEVEL, level).apply() 42 if (level == 0) restore(done) else write(level, done) 43 } 44 45 // ---- from a key --------------------------------------------------------------------- 46 47 /** 48 * The order of brightness, darkest last: off (the system level), then the 49 * levels under the floor from high to low. [darker] is one step down that 50 * order and [brighter] one step up; both stop at the ends. 51 */ 52 fun darker(level: Int, levels: List<Int>): Int = when { 53 levels.isEmpty() -> 0 54 level == 0 -> levels.first() 55 else -> levels.getOrElse(levels.indexOf(level) + 1) { levels.last() } 56 } 57 58 fun brighter(level: Int, levels: List<Int>): Int { 59 val i = levels.indexOf(level) 60 return if (i <= 0) 0 else levels[i - 1] 61 } 62 63 /** 64 * On and off. On is the lowest level there is. Off gives the light back to 65 * the brightness the user set in the system. 66 */ 67 fun toggled(level: Int, levels: List<Int>): Int = if (level > 0) 0 else levels.lastOrNull() ?: 0 68 69 /** 70 * One step of the system brightness, 0..255. A step is a quarter of the 71 * present value and at least 5, so steps are fine where the light is low and 72 * coarse where it is high. The result stays between [floor] and 255. 73 */ 74 fun systemStep(value: Int, up: Boolean, floor: Int): Int { 75 val step = maxOf(5, value / 4) 76 return (if (up) value + step else value - step).coerceIn(floor, 255) 77 } 78 79 private fun systemBrightness(c: Context, up: Boolean, done: (Boolean) -> Unit) { 80 // Brighter from an extra-dim level means: leave extra-dim first. 81 if (up && level(c) > 0) return set(c, 0, done) 82 Shell.run("settings get system screen_brightness") { r -> 83 val now = r.output.trim().toIntOrNull() ?: return@run done(false) 84 val next = systemStep(now, up, Device.brightnessFloor ?: 1) 85 Shell.run("settings put system screen_brightness_mode 0; settings put system screen_brightness $next") { 86 done(it.ok) 87 } 88 } 89 } 90 91 /** Runs a key action: "toggle", "darker", "brighter", "system_up" or "system_down". */ 92 fun act(c: Context, what: String, done: (Boolean) -> Unit = {}) { 93 if (what == "system_up") return systemBrightness(c, true, done) 94 if (what == "system_down") return systemBrightness(c, false, done) 95 val now = level(c) 96 val next = when (what) { 97 "darker" -> darker(now, levels()) 98 "brighter" -> brighter(now, levels()) 99 else -> toggled(now, levels()) 100 } 101 if (next == now) return done(true) 102 set(c, next, done) 103 } 104 105 /** Re-asserts the chosen level; called when the screen comes on. */ 106 fun reapply(c: Context) { 107 val l = level(c) 108 if (l > 0 && Shell.ready) write(l) {} 109 } 110 111 private fun write(value: Int, done: (Boolean) -> Unit) { 112 // First node that exists; plain write, then through su if refused. 113 val script = nodes.joinToString(" ") { "'$it'" }.let { list -> 114 "for n in $list; do [ -e \$n ] || continue; " + 115 "echo $value > \$n 2>/dev/null || su 0 sh -c \"echo $value > \$n\"; " + 116 "[ \"\$(cat \$n)\" = \"$value\" ] && exit 0; exit 1; done; exit 2" 117 } 118 Shell.run(script) { done(it.ok) } 119 } 120 121 /** Hands the light back by nudging the system's own brightness setting. */ 122 private fun restore(done: (Boolean) -> Unit) { 123 Shell.run( 124 "v=\$(settings get system screen_brightness); " + 125 "settings put system screen_brightness \$((v+1)); settings put system screen_brightness \$v" 126 ) { done(it.ok) } 127 } 128 }