Skip to content

Commit dbcdbbc

Browse files
authored
Merge pull request #200 from Hepolise/dev
feat: add configurable long press actions with quick settings tile
2 parents b0dc5f1 + 3fadd64 commit dbcdbbc

File tree

14 files changed

+375
-22
lines changed

14 files changed

+375
-22
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@
4444
</intent-filter>
4545
</receiver>
4646

47+
<service
48+
android:name="ru.hepolise.volumekeytrackcontrol.service.RewindActionTileService"
49+
android:exported="true"
50+
android:label="@string/long_press_action"
51+
android:icon="@drawable/ic_skip_next_48dp"
52+
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
53+
<intent-filter>
54+
<action android:name="android.service.quicksettings.action.QS_TILE" />
55+
</intent-filter>
56+
</service>
57+
4758
<!-- metadata -->
4859
<meta-data
4960
android:name="xposedmodule"

app/src/main/java/ru/hepolise/volumekeytrackcontrol/module/VolumeKeyControlModuleHandlers.kt

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.content.SharedPreferences
55
import android.hardware.display.DisplayManager
66
import android.media.AudioManager
7+
import android.media.MediaMetadata
78
import android.media.session.MediaController
89
import android.media.session.PlaybackState
910
import android.os.Handler
@@ -19,15 +20,20 @@ import ru.hepolise.volumekeytrackcontrol.module.util.LogHelper
1920
import ru.hepolise.volumekeytrackcontrol.module.util.RemotePrefsHelper
2021
import ru.hepolise.volumekeytrackcontrol.module.util.StatusHelper
2122
import ru.hepolise.volumekeytrackcontrol.util.AppFilterType
23+
import ru.hepolise.volumekeytrackcontrol.util.RewindActionType
2224
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil
2325
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.LAUNCHED_COUNT
2426
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getAppFilterType
2527
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getApps
2628
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLaunchedCount
2729
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getLongPressDuration
30+
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getRewindActionType
31+
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getRewindDuration
2832
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.isSwapButtons
2933
import ru.hepolise.volumekeytrackcontrol.util.VibratorUtil.getVibrator
3034
import ru.hepolise.volumekeytrackcontrol.util.VibratorUtil.triggerVibration
35+
import kotlin.math.max
36+
import kotlin.math.min
3137

3238

3339
object VolumeKeyControlModuleHandlers {
@@ -43,6 +49,7 @@ object VolumeKeyControlModuleHandlers {
4349
private lateinit var powerManager: PowerManager
4450
private lateinit var displayManager: DisplayManager
4551
private lateinit var vibrator: Vibrator
52+
private lateinit var sessionHelper: Any
4653

4754
private var prefs: SharedPreferences? = null
4855

@@ -129,31 +136,35 @@ object VolumeKeyControlModuleHandlers {
129136
displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager?
130137
?: throw NullPointerException("Unable to obtain display service")
131138
vibrator = getVibrator()
139+
sessionHelper = getMediaSessionLegacyHelper()
132140
}
133141

134142
private fun initPrefs() {
135143
prefs = SharedPreferencesUtil.prefs()
136144
}
137145

138-
private fun Context.initControllers() {
146+
private fun Context.getMediaSessionLegacyHelper(): Any {
139147
val context = this
140148
val classLoader = javaClass.classLoader
141149
val mediaSessionHelperClass = XposedHelpers.findClass(
142150
CLASS_MEDIA_SESSION_LEGACY_HELPER,
143151
classLoader
144152
)
145-
val helper = XposedHelpers.callStaticMethod(
153+
return XposedHelpers.callStaticMethod(
146154
mediaSessionHelperClass,
147155
"getHelper",
148156
context
149157
)
150-
val mSessionManager = XposedHelpers.getObjectField(helper, "mSessionManager")
158+
}
159+
160+
private fun Context.initControllers() {
161+
val sessionManager = XposedHelpers.getObjectField(sessionHelper, "mSessionManager")
151162
val componentNameClass =
152163
XposedHelpers.findClass(CLASS_COMPONENT_NAME, classLoader)
153164

154165
@Suppress("UNCHECKED_CAST")
155166
mediaControllers = XposedHelpers.callMethod(
156-
mSessionManager,
167+
sessionManager,
157168
"getActiveSessions",
158169
arrayOf(componentNameClass),
159170
null
@@ -191,7 +202,7 @@ object VolumeKeyControlModuleHandlers {
191202
log("Volume unpressed action received, down: $isDownPressed, up: $isUpPressed")
192203
abortAll()
193204
if (!isLongPress && getMediaController().isMusicActive()) {
194-
log("Adjusting music volume")
205+
log("Adjusting stream volume")
195206
keyHelper.adjustStreamVolume(audioManager)
196207
}
197208
}
@@ -239,8 +250,29 @@ object VolumeKeyControlModuleHandlers {
239250
if (controller.isMusicActive()) controls.pause() else controls.play()
240251
}
241252

242-
MediaEvent.Next -> controls.skipToNext()
243-
MediaEvent.Prev -> controls.skipToPrevious()
253+
MediaEvent.Next -> {
254+
if (prefs.getRewindActionType() == RewindActionType.TRACK_CHANGE) {
255+
controls.skipToNext()
256+
} else {
257+
val currentPosition = controller.playbackState?.position ?: 0
258+
val duration =
259+
controller.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)
260+
?: Long.MAX_VALUE
261+
val newPosition =
262+
min(currentPosition + prefs.getRewindDuration() * 1000, duration)
263+
controls.seekTo(newPosition)
264+
}
265+
}
266+
267+
MediaEvent.Prev -> {
268+
if (prefs.getRewindActionType() == RewindActionType.TRACK_CHANGE) {
269+
controls.skipToPrevious()
270+
} else {
271+
val currentPosition = controller.playbackState?.position ?: 0
272+
val newPosition = max(currentPosition - prefs.getRewindDuration() * 1000, 0)
273+
controls.seekTo(newPosition)
274+
}
275+
}
244276
}
245277
vibrator.triggerVibration()
246278
}
@@ -339,11 +371,32 @@ object VolumeKeyControlModuleHandlers {
339371
}
340372

341373
fun adjustStreamVolume(audioManager: AudioManager) {
342-
val direction = when (origKey) {
343-
Key.UP -> AudioManager.ADJUST_RAISE
344-
Key.DOWN -> AudioManager.ADJUST_LOWER
374+
try {
375+
val direction = when (origKey) {
376+
Key.UP -> KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_UP)
377+
Key.DOWN -> KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_VOLUME_DOWN)
378+
}
379+
380+
XposedHelpers.callMethod(
381+
sessionHelper,
382+
"sendVolumeKeyEvent",
383+
arrayOf(
384+
KeyEvent::class.java,
385+
Int::class.javaPrimitiveType,
386+
Boolean::class.javaPrimitiveType
387+
),
388+
direction, AudioManager.USE_DEFAULT_STREAM_TYPE, false
389+
)
390+
} catch (e: Exception) {
391+
log("Failed to adjust stream volume: ${e.message}")
392+
log("Falling back to adjustStreamVolume")
393+
394+
val direction = when (origKey) {
395+
Key.UP -> AudioManager.ADJUST_RAISE
396+
Key.DOWN -> AudioManager.ADJUST_LOWER
397+
}
398+
audioManager.adjustStreamVolume(AudioManager.USE_DEFAULT_STREAM_TYPE, direction, 0)
345399
}
346-
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, 0)
347400
}
348401

349402
private companion object {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package ru.hepolise.volumekeytrackcontrol.service
2+
3+
import android.graphics.drawable.Icon
4+
import android.service.quicksettings.Tile
5+
import android.service.quicksettings.TileService
6+
import androidx.core.content.edit
7+
import ru.hepolise.volumekeytrackcontrol.util.RewindActionType
8+
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.REWIND_ACTION_TYPE
9+
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getRewindActionType
10+
import ru.hepolise.volumekeytrackcontrol.util.SharedPreferencesUtil.getSettingsSharedPreferences
11+
import ru.hepolise.volumekeytrackcontrolmodule.R
12+
13+
class RewindActionTileService : TileService() {
14+
15+
override fun onStartListening() {
16+
super.onStartListening()
17+
updateTile()
18+
}
19+
20+
override fun onClick() {
21+
super.onClick()
22+
toggleActionType()
23+
}
24+
25+
private fun toggleActionType() {
26+
val prefs = getSettingsSharedPreferences()
27+
val currentType = prefs.getRewindActionType()
28+
29+
val newType = when (currentType) {
30+
RewindActionType.TRACK_CHANGE -> RewindActionType.REWIND
31+
RewindActionType.REWIND -> RewindActionType.TRACK_CHANGE
32+
}
33+
34+
prefs?.edit {
35+
putString(REWIND_ACTION_TYPE, newType.name)
36+
}
37+
38+
updateTile()
39+
}
40+
41+
private fun updateTile() {
42+
val prefs = getSettingsSharedPreferences()
43+
val currentType = prefs.getRewindActionType()
44+
45+
val tile = qsTile ?: return
46+
47+
when (currentType) {
48+
RewindActionType.TRACK_CHANGE -> {
49+
tile.label = getString(R.string.track_change)
50+
tile.contentDescription = getString(R.string.track_change)
51+
tile.icon = Icon.createWithResource(this, R.drawable.ic_skip_next_48dp)
52+
tile.state = Tile.STATE_ACTIVE
53+
}
54+
55+
RewindActionType.REWIND -> {
56+
tile.label = getString(R.string.rewind)
57+
tile.contentDescription = getString(R.string.rewind)
58+
tile.icon = Icon.createWithResource(this, R.drawable.ic_fast_forward_48dp)
59+
tile.state = Tile.STATE_ACTIVE
60+
}
61+
}
62+
63+
tile.updateTile()
64+
}
65+
}

0 commit comments

Comments
 (0)