Skip to content

Commit e6fd6a1

Browse files
committed
Godmode!
1 parent 9930ec0 commit e6fd6a1

8 files changed

Lines changed: 120 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- **Enable 360° setting**: new toggle in Overlay Settings switches the joystick from 4-direction keycode mode to the analog `joy` intent protocol (`com.thorkracing.wireddevices.keypress`, `deviceName=Remote2`). Supports diagonal input and 4 deflection magnitudes (2–5) with a dead zone. The running overlay reacts to the setting change without restart.
1112
- `OverlayControlReceiver`: exported `BroadcastReceiver` to enable/disable the overlay via ADB intents.
12-
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_SHOW` — starts the overlay (no-op if already running or overlay permission not granted)
13-
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_HIDE` — stops the overlay (no-op if not running)
14-
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_TOGGLE` — toggles the overlay on/off
13+
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_SHOW -p de.codevoid.andremote2` — starts the overlay (no-op if already running or overlay permission not granted)
14+
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_HIDE -p de.codevoid.andremote2` — stops the overlay (no-op if not running)
15+
- `adb shell am broadcast -a de.codevoid.andremote2.OVERLAY_TOGGLE -p de.codevoid.andremote2` — toggles the overlay on/off
16+
- Note: `-p de.codevoid.andremote2` is required — Android 8+ does not deliver implicit broadcasts to manifest receivers

app/src/main/java/de/codevoid/andremote2/MainActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity
2222
import androidx.lifecycle.lifecycleScope
2323
import com.google.android.material.appbar.MaterialToolbar
2424
import com.google.android.material.button.MaterialButton
25+
import com.google.android.material.materialswitch.MaterialSwitch
2526
import com.google.android.material.slider.Slider
2627
import com.google.android.material.snackbar.Snackbar
2728
import kotlinx.coroutines.Dispatchers
@@ -38,6 +39,7 @@ class MainActivity : AppCompatActivity() {
3839
private lateinit var sliderOpacity: Slider
3940
private lateinit var tvSize: TextView
4041
private lateinit var tvOpacity: TextView
42+
private lateinit var switchAnalog: MaterialSwitch
4143
private lateinit var btnCheckUpdates: MaterialButton
4244

4345
private var activeDownloadId: Long = -1
@@ -57,6 +59,7 @@ class MainActivity : AppCompatActivity() {
5759
sliderOpacity = findViewById(R.id.sliderOpacity)
5860
tvSize = findViewById(R.id.tvSize)
5961
tvOpacity = findViewById(R.id.tvOpacity)
62+
switchAnalog = findViewById(R.id.switchAnalog)
6063
btnCheckUpdates = findViewById(R.id.btnCheckUpdates)
6164

6265
loadSettings()
@@ -73,6 +76,10 @@ class MainActivity : AppCompatActivity() {
7376
if (fromUser) prefs.edit().putInt(PrefKeys.OVERLAY_OPACITY, opacity).apply()
7477
}
7578

79+
switchAnalog.setOnCheckedChangeListener { _, checked ->
80+
prefs.edit().putBoolean(PrefKeys.JOYSTICK_ANALOG, checked).apply()
81+
}
82+
7683
btnToggleOverlay.setOnClickListener {
7784
if (!Settings.canDrawOverlays(this)) {
7885
requestOverlayPermission()
@@ -129,6 +136,7 @@ class MainActivity : AppCompatActivity() {
129136
sliderOpacity.value = opacity.toFloat()
130137
tvSize.text = "$size%"
131138
tvOpacity.text = "$opacity%"
139+
switchAnalog.isChecked = prefs.getBoolean(PrefKeys.JOYSTICK_ANALOG, false)
132140
}
133141

134142
private fun requestOverlayPermission() {

app/src/main/java/de/codevoid/andremote2/OverlayService.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import android.view.LayoutInflater
1616
import android.view.View
1717
import android.view.WindowManager
1818
import androidx.core.app.NotificationCompat
19+
import de.codevoid.andremote2.views.JoystickView
1920

2021
class OverlayService : Service() {
2122

@@ -37,8 +38,11 @@ class OverlayService : Service() {
3738
private val mainHandler = Handler(Looper.getMainLooper())
3839

3940
private val prefListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
40-
if (key == PrefKeys.OVERLAY_SIZE || key == PrefKeys.OVERLAY_OPACITY) {
41-
mainHandler.post { applyScaleAndAlpha() }
41+
when (key) {
42+
PrefKeys.OVERLAY_SIZE, PrefKeys.OVERLAY_OPACITY ->
43+
mainHandler.post { applyScaleAndAlpha() }
44+
PrefKeys.JOYSTICK_ANALOG ->
45+
mainHandler.post { applyAnalogMode() }
4246
}
4347
}
4448

@@ -120,6 +124,8 @@ class OverlayService : Service() {
120124

121125
applyScaleAndAlpha()
122126

127+
applyAnalogMode()
128+
123129
// Set bottom button to keycode 111 (ROUND BUTTON 2 per protocol)
124130
val buttonBottom = overlayView.findViewById<de.codevoid.andremote2.views.ButtonView>(R.id.buttonBottom)
125131
buttonBottom.setKeyCode(111)
@@ -138,6 +144,11 @@ class OverlayService : Service() {
138144
windowManager.addView(overlayView, overlayParams)
139145
}
140146

147+
private fun applyAnalogMode() {
148+
overlayView.findViewById<JoystickView>(R.id.joystickView).analogMode =
149+
prefs.getBoolean(PrefKeys.JOYSTICK_ANALOG, false)
150+
}
151+
141152
private fun applyScaleAndAlpha() {
142153
val size = prefs.getInt(PrefKeys.OVERLAY_SIZE, 75).coerceIn(10, 200)
143154
val opacity = prefs.getInt(PrefKeys.OVERLAY_OPACITY, 80).coerceIn(0, 100)

app/src/main/java/de/codevoid/andremote2/PrefKeys.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ object PrefKeys {
55
const val OVERLAY_OPACITY = "overlay_opacity"
66
const val OVERLAY_X = "overlay_x"
77
const val OVERLAY_Y = "overlay_y"
8+
const val JOYSTICK_ANALOG = "joystick_analog"
89
}

app/src/main/java/de/codevoid/andremote2/RemoteControl.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@ object RemoteControl {
2020
putExtra("deviceName", DEVICE_NAME)
2121
})
2222
}
23+
24+
fun sendJoy(context: Context, joy: String) {
25+
context.sendBroadcast(Intent(ACTION).apply {
26+
putExtra("joy", joy)
27+
putExtra("deviceName", DEVICE_NAME)
28+
})
29+
}
2330
}

app/src/main/java/de/codevoid/andremote2/views/JoystickView.kt

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class JoystickView @JvmOverloads constructor(
1818
attrs: AttributeSet? = null
1919
) : View(context, attrs) {
2020

21+
var analogMode = false
22+
2123
private var keycodeUp = 19
2224
private var keycodeDown = 20
2325
private var keycodeLeft = 21
@@ -48,6 +50,7 @@ class JoystickView @JvmOverloads constructor(
4850
private var knobX = 0f
4951
private var knobY = 0f
5052
private var currentKeyCode = -1
53+
private var lastJoyString = ""
5154

5255
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
5356
centerX = w / 2f
@@ -98,31 +101,61 @@ class JoystickView @JvmOverloads constructor(
98101
knobY = centerY + dy * ratio
99102
invalidate()
100103

101-
if (dist > baseRadius * 0.3f) {
102-
val direction = getDirection(dx, dy)
103-
if (direction != currentKeyCode) {
104-
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
105-
currentKeyCode = direction
106-
RemoteControl.sendPress(context, direction)
104+
if (analogMode) {
105+
val joy = buildJoyString(dx * ratio, dy * ratio, maxDist)
106+
if (joy != lastJoyString) {
107+
lastJoyString = joy
108+
RemoteControl.sendJoy(context, joy)
107109
}
108110
} else {
109-
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
110-
currentKeyCode = -1
111+
if (dist > baseRadius * 0.3f) {
112+
val direction = getDirection(dx, dy)
113+
if (direction != currentKeyCode) {
114+
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
115+
currentKeyCode = direction
116+
RemoteControl.sendPress(context, direction)
117+
}
118+
} else {
119+
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
120+
currentKeyCode = -1
121+
}
111122
}
112123
return true
113124
}
114125
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
115-
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
126+
if (analogMode) {
127+
if (lastJoyString != "Y0X0") RemoteControl.sendJoy(context, "Y0X0")
128+
lastJoyString = ""
129+
} else {
130+
if (currentKeyCode != -1) RemoteControl.sendRelease(context, currentKeyCode)
131+
currentKeyCode = -1
132+
}
116133
knobX = centerX
117134
knobY = centerY
118-
currentKeyCode = -1
119135
invalidate()
120136
return true
121137
}
122138
}
123139
return super.onTouchEvent(event)
124140
}
125141

142+
private fun buildJoyString(dx: Float, dy: Float, maxDist: Float): String {
143+
if (maxDist == 0f) return "Y0X0"
144+
val xNorm = (abs(dx) / maxDist).coerceIn(0f, 1f)
145+
val yNorm = (abs(dy) / maxDist).coerceIn(0f, 1f)
146+
val yPart = toMagnitude(yNorm)?.let { mag -> if (dy < 0) "U$mag" else "D$mag" } ?: ""
147+
val xPart = toMagnitude(xNorm)?.let { mag -> if (dx < 0) "L$mag" else "R$mag" } ?: ""
148+
return if (yPart.isEmpty() && xPart.isEmpty()) "Y0X0" else "$yPart$xPart"
149+
}
150+
151+
private fun toMagnitude(norm: Float): Int? = when {
152+
norm < 0.25f -> null
153+
norm < 0.50f -> 2
154+
norm < 0.70f -> 3
155+
norm < 0.85f -> 4
156+
else -> 5
157+
}
158+
126159
private fun getDirection(dx: Float, dy: Float): Int {
127160
return if (abs(dx) > abs(dy)) {
128161
if (dx > 0) keycodeRight else keycodeLeft

app/src/main/res/layout/activity_main.xml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,47 @@
137137

138138
</LinearLayout>
139139

140+
<com.google.android.material.divider.MaterialDivider
141+
android:layout_width="match_parent"
142+
android:layout_height="wrap_content"
143+
android:layout_marginTop="12dp"
144+
android:layout_marginBottom="4dp"/>
145+
146+
<LinearLayout
147+
android:layout_width="match_parent"
148+
android:layout_height="wrap_content"
149+
android:orientation="horizontal"
150+
android:gravity="center_vertical">
151+
152+
<LinearLayout
153+
android:layout_width="0dp"
154+
android:layout_height="wrap_content"
155+
android:layout_weight="1"
156+
android:orientation="vertical">
157+
158+
<TextView
159+
android:layout_width="wrap_content"
160+
android:layout_height="wrap_content"
161+
android:text="@string/joystick_360_title"
162+
android:textStyle="bold"/>
163+
164+
<TextView
165+
android:layout_width="wrap_content"
166+
android:layout_height="wrap_content"
167+
android:text="@string/joystick_360_desc"
168+
android:textSize="12sp"
169+
android:alpha="0.7"/>
170+
171+
</LinearLayout>
172+
173+
<com.google.android.material.materialswitch.MaterialSwitch
174+
android:id="@+id/switchAnalog"
175+
android:layout_width="wrap_content"
176+
android:layout_height="wrap_content"
177+
android:layout_marginStart="8dp"/>
178+
179+
</LinearLayout>
180+
140181
</LinearLayout>
141182

142183
</com.google.android.material.card.MaterialCardView>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,6 @@
4141
<string name="update_check_failed">Update check failed</string>
4242
<string name="update_download_failed">Download failed</string>
4343
<string name="allow_installs_prompt">Allow installing apps from this source to proceed.</string>
44+
<string name="joystick_360_title">Enable 360°</string>
45+
<string name="joystick_360_desc">Use analog joy sensor instead of D-pad keycodes</string>
4446
</resources>

0 commit comments

Comments
 (0)