diff --git a/app/build.gradle b/app/build.gradle
index 78d2fac..dce0baf 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,18 +2,18 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android'
- id 'kotlin-android-extensions'
+
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
}
android {
- compileSdk 33
+ compileSdk 36
defaultConfig {
applicationId 'app.screenreader'
minSdk 24
- targetSdk 33
+ targetSdk 36
versionCode 9
versionName '1.1.0'
}
@@ -29,11 +29,11 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
}
namespace 'app.screenreader'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a0ed5a4..1750a72 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,5 +31,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/app/screenreader/extensions/_Context.kt b/app/src/main/java/app/screenreader/extensions/_Context.kt
index 317d6a1..956dd7a 100644
--- a/app/src/main/java/app/screenreader/extensions/_Context.kt
+++ b/app/src/main/java/app/screenreader/extensions/_Context.kt
@@ -1,8 +1,13 @@
package app.screenreader.extensions
+import android.annotation.SuppressLint
import android.app.Activity
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.IntentFilter
import android.net.Uri
+import android.os.Build
+import android.os.Build.VERSION.SDK_INT
import android.os.LocaleList
import android.text.SpannableString
import android.text.Spanned
@@ -164,4 +169,23 @@ fun Context.openWebsite(uri: Uri) {
.build()
intent.launchUrl(this, uri)
+}
+
+@SuppressLint("UnspecifiedRegisterReceiverFlag")
+fun Context.registerBroadcastReceiver(
+ broadcastReceiver: BroadcastReceiver,
+ intentFilter: IntentFilter?,
+ exported: Boolean = true,
+) {
+
+ when {
+ SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
+ val exportedFlag =
+ if (exported) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED
+
+ registerReceiver(broadcastReceiver, intentFilter, exportedFlag)
+ }
+
+ else -> registerReceiver(broadcastReceiver, intentFilter)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/app/screenreader/model/Constants.kt b/app/src/main/java/app/screenreader/model/Constants.kt
index 88b4d65..84d3619 100644
--- a/app/src/main/java/app/screenreader/model/Constants.kt
+++ b/app/src/main/java/app/screenreader/model/Constants.kt
@@ -11,5 +11,6 @@ class Constants {
val SERVICE_ACTION = "SCREENREADER_SERVICE"
val SERVICE_GESTURE = "GESTURE"
val SERVICE_KILLED = "KILLED"
+ val SERVICE_MOTION_EVENT = "MOTION_EVENT"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/app/screenreader/services/ScreenReaderService.kt b/app/src/main/java/app/screenreader/services/ScreenReaderService.kt
index 7a10e0d..1248a58 100644
--- a/app/src/main/java/app/screenreader/services/ScreenReaderService.kt
+++ b/app/src/main/java/app/screenreader/services/ScreenReaderService.kt
@@ -2,6 +2,7 @@ package app.screenreader.services
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
+import android.accessibilityservice.TouchInteractionController
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
@@ -10,6 +11,7 @@ import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.KeyEvent
+import android.view.MotionEvent
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
@@ -18,6 +20,8 @@ import androidx.core.content.ContextCompat
import androidx.core.hardware.display.DisplayManagerCompat
import app.screenreader.MainActivity
import app.screenreader.R
+import app.screenreader.tabs.actions.ActionActivity
+import app.screenreader.tabs.gestures.GestureActivity
import app.screenreader.extensions.getSpannable
import app.screenreader.model.Constants
import app.screenreader.model.Gesture
@@ -34,16 +38,18 @@ import java.io.Serializable
class ScreenReaderService: AccessibilityService() {
private val TAG = "ScreenReaderService"
- private val GESTURE_TRAINING_CLASS_NAME = MainActivity::class.java.name
+ private val MAIN_ACTIVITY_CLASS_NAME = MainActivity::class.java.name
+ private val GESTURE_ACTIVITY_CLASS_NAME = GestureActivity::class.java.name
+ private val ACTION_ACTIVITY_CLASS_NAME = ActionActivity::class.java.name
+
+ private var touchController: TouchInteractionController? = null
+ private var touchControllerCallback: TouchInteractionController.Callback? = null
override fun onCreate() {
super.onCreate()
Log.i(TAG, "onCreate")
- // Set passthrough regions
setPassthroughRegions()
-
- // Start GestureActivity
startGestureTraining()
}
@@ -71,10 +77,95 @@ class ScreenReaderService: AccessibilityService() {
override fun onServiceConnected() {
Log.i(TAG, "Service connected")
super.onServiceConnected()
+
+ // Setup TouchInteractionController (API 32+) to intercept touch events
+ // and pass them through to the app via requestDelegating(), bypassing TalkBack's gesture handling
+ setupTouchInteractionController()
+ }
+
+ private fun setupTouchInteractionController() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
+ try {
+ // Use default display (ID = 0)
+ val displayId = android.view.Display.DEFAULT_DISPLAY
+
+ touchController = getTouchInteractionController(displayId)
+ Log.i(TAG, "Got TouchInteractionController for display $displayId")
+
+ touchControllerCallback = object : TouchInteractionController.Callback {
+ override fun onMotionEvent(event: MotionEvent) {
+ Log.d(TAG, "TouchController onMotionEvent: action=${event.action}, pointerCount=${event.pointerCount}")
+
+ // When gesture training is active, request delegating to pass ALL events
+ // through to the app without TalkBack processing them.
+ // This allows the app's gesture recognizers to handle both swipes and taps.
+ if (isGestureTraining()) {
+ touchController?.let { controller ->
+ if (controller.state == TouchInteractionController.STATE_TOUCH_INTERACTING) {
+ Log.d(TAG, "Requesting delegating mode to bypass TalkBack")
+ controller.requestDelegating()
+ }
+ }
+ }
+ }
+
+ override fun onStateChanged(state: Int) {
+ val stateName = when (state) {
+ TouchInteractionController.STATE_CLEAR -> "CLEAR"
+ TouchInteractionController.STATE_TOUCH_INTERACTING -> "TOUCH_INTERACTING"
+ TouchInteractionController.STATE_TOUCH_EXPLORING -> "TOUCH_EXPLORING"
+ TouchInteractionController.STATE_DRAGGING -> "DRAGGING"
+ TouchInteractionController.STATE_DELEGATING -> "DELEGATING"
+ else -> "UNKNOWN($state)"
+ }
+ Log.d(TAG, "TouchController state changed to: $stateName")
+ }
+ }
+
+ touchController?.registerCallback(mainExecutor, touchControllerCallback!!)
+ Log.i(TAG, "Registered TouchInteractionController callback")
+
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to setup TouchInteractionController", e)
+ }
+ }
+ }
+
+ /**
+ * Called when raw motion events are received from the configured motion event sources.
+ * On API 32+, we use TouchInteractionController with requestDelegating() instead,
+ * which passes events directly to the app's normal touch pipeline.
+ */
+ override fun onMotionEvent(event: MotionEvent) {
+ Log.d(TAG, "onMotionEvent: action=${event.action}, pointerCount=${event.pointerCount}, x=${event.x}, y=${event.y}")
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) {
+ if (isGestureTraining()) {
+ broadcastMotionEvent(event)
+ }
+ }
+ }
+
+ private fun broadcastMotionEvent(event: MotionEvent) {
+ val intent = Intent(Constants.SERVICE_ACTION)
+ intent.setPackage(packageName)
+ // MotionEvent must be copied because the original may be recycled
+ intent.putExtra(Constants.SERVICE_MOTION_EVENT, MotionEvent.obtain(event))
+ sendBroadcast(intent)
}
override fun onUnbind(intent: Intent?): Boolean {
Log.i(TAG, "onUnbind")
+
+ // Cleanup TouchInteractionController
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
+ touchControllerCallback?.let { callback ->
+ touchController?.unregisterCallback(callback)
+ }
+ touchController = null
+ touchControllerCallback = null
+ }
+
return super.onUnbind(intent)
}
@@ -85,34 +176,28 @@ class ScreenReaderService: AccessibilityService() {
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
Log.i(TAG, "onAccessibilityEvent: $event")
- // Continue if eventType = TYPE_WINDOW_STATE_CHANGED
if (event == null || event.eventType != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
return
}
- // Continue if packageName is empty
if (event.packageName == null || event.packageName.isEmpty()) {
return
}
- // Continue if event does not come from own package
+
if (event.packageName == this.packageName) {
return
}
-
- // Continue if event does not come from accessibility package
if (event.packageName.contains("accessibility")) {
return
}
- // Continue if text does not contain the service label
val serviceName = getString(R.string.service_label)
if (event.text.contains(serviceName)) {
return
}
- // Continue if the gesture training is not active
- if (isGestureTraining()) {
+ if (isInApp()) {
return
}
@@ -121,11 +206,16 @@ class ScreenReaderService: AccessibilityService() {
}
override fun onGesture(gestureId: Int): Boolean {
- Log.i(TAG, "onGesture: $gestureId")
+ Log.i(TAG, "onGesture called with gestureId: $gestureId")
// Broadcast gesture to GestureActivity
- Gesture.from(gestureId)?.let { gesture ->
+ val gesture = Gesture.from(gestureId)
+ Log.i(TAG, "Mapped gestureId $gestureId to gesture: $gesture")
+
+ if (gesture != null) {
broadcast(Constants.SERVICE_GESTURE, gesture)
+ } else {
+ Log.w(TAG, "Unknown gestureId: $gestureId - not mapped to any Gesture")
}
// Kill service if touch exploration is disabled
@@ -158,7 +248,6 @@ class ScreenReaderService: AccessibilityService() {
val flags = service.capabilities
val capability = AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
- // Check if Touch Exploration capability is granted
if (flags and capability == capability) {
count++
}
@@ -171,7 +260,19 @@ class ScreenReaderService: AccessibilityService() {
private fun isGestureTraining(): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getRunningTasks(1).firstOrNull()?.topActivity?.let { activity ->
- return activity.className == GESTURE_TRAINING_CLASS_NAME
+ // Check if the user is in the GestureActivity (where gesture training happens)
+ return activity.className == GESTURE_ACTIVITY_CLASS_NAME
+ }
+ return false
+ }
+
+ private fun isInApp(): Boolean {
+ val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ activityManager.getRunningTasks(1).firstOrNull()?.topActivity?.let { activity ->
+ // Check if user is in any of the app's activities
+ return activity.className == MAIN_ACTIVITY_CLASS_NAME ||
+ activity.className == GESTURE_ACTIVITY_CLASS_NAME ||
+ activity.className == ACTION_ACTIVITY_CLASS_NAME
}
return false
}
@@ -181,15 +282,15 @@ class ScreenReaderService: AccessibilityService() {
gestures.forEach { gesture ->
gesture.completed(this, false)
}
-
-// val intent = Intent(this, GestureActivity::class.java)
-// intent.setGestures(gestures)
-// intent.setInstructions(instructions)
-// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-// startActivity(intent)
}
companion object {
+ const val MIN_API_FOR_TALKBACK_COMPATIBILITY = Build.VERSION_CODES.TIRAMISU
+
+ fun supportsTalkBackCompatibility(): Boolean {
+ return Build.VERSION.SDK_INT >= MIN_API_FOR_TALKBACK_COMPATIBILITY
+ }
+
fun isEnabled(context: Context): Boolean {
(context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager).let { manager ->
val services = manager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
diff --git a/app/src/main/java/app/screenreader/tabs/actions/ActionActivity.kt b/app/src/main/java/app/screenreader/tabs/actions/ActionActivity.kt
index bd0995a..56bb5f7 100644
--- a/app/src/main/java/app/screenreader/tabs/actions/ActionActivity.kt
+++ b/app/src/main/java/app/screenreader/tabs/actions/ActionActivity.kt
@@ -3,16 +3,15 @@ package app.screenreader.tabs.actions
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Intent
import android.text.SpannableString
+import android.widget.ScrollView
import app.screenreader.R
import app.screenreader.extensions.doGetAction
-import app.screenreader.extensions.identifier
import app.screenreader.extensions.showDialog
import app.screenreader.helpers.Accessibility
import app.screenreader.helpers.Events
import app.screenreader.model.Action
import app.screenreader.views.actions.ActionViewCallback
import app.screenreader.widgets.ToolbarActivity
-import kotlinx.android.synthetic.main.activity_action.*
/**
* Created by Jan Jaap de Groot on 16/11/2020
@@ -22,6 +21,7 @@ class ActionActivity: ToolbarActivity(), ActionViewCallback {
private val startTime = System.currentTimeMillis()
+ private val scrollView get() = findViewById(R.id.scrollView)
private val action: Action by lazy {
intent.doGetAction() ?: Action.SELECT
}
diff --git a/app/src/main/java/app/screenreader/tabs/gestures/GestureActivity.kt b/app/src/main/java/app/screenreader/tabs/gestures/GestureActivity.kt
index 5bd6e8b..334df32 100644
--- a/app/src/main/java/app/screenreader/tabs/gestures/GestureActivity.kt
+++ b/app/src/main/java/app/screenreader/tabs/gestures/GestureActivity.kt
@@ -3,12 +3,15 @@ package app.screenreader.tabs.gestures
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.*
+import android.os.Build
import android.text.SpannableString
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
+import android.view.MotionEvent
import android.view.View
+import android.widget.FrameLayout
import androidx.appcompat.app.AlertDialog
import app.screenreader.R
import app.screenreader.extensions.*
@@ -20,7 +23,6 @@ import app.screenreader.services.ScreenReaderService
import app.screenreader.views.gestures.GestureView
import app.screenreader.views.gestures.GestureViewCallback
import app.screenreader.widgets.ToolbarActivity
-import kotlinx.android.synthetic.main.activity_gesture.*
import java.util.*
import kotlin.concurrent.schedule
@@ -51,6 +53,12 @@ class GestureActivity: ToolbarActivity(), GestureViewCallback {
private var errorCount = 0
private var finished = false
+ private val container get() = findViewById(R.id.container)
+ private val titleTextView get() = findViewById(R.id.titleTextView)
+ private val descriptionTextView get() = findViewById(R.id.descriptionTextView)
+ private val gestureImageView get() = findViewById(R.id.gestureImageView)
+ private val feedbackTextView get() = findViewById(R.id.feedbackTextView)
+
private val isPracticing: Boolean
get() = gestures.isNotEmpty()
@@ -69,6 +77,15 @@ class GestureActivity: ToolbarActivity(), GestureViewCallback {
gestureView.onAccessibilityGesture(gesture) // Pass gesture to GestureView.
}
}
+
+ // Received motion event (from ScreenReaderService intercepting raw touch events)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ (intent?.getParcelableExtra(Constants.SERVICE_MOTION_EVENT, MotionEvent::class.java))?.let { event ->
+ Log.d(TAG, "Received motion event from service: action=${event.action}, pointerCount=${event.pointerCount}")
+ gestureView.dispatchTouchEvent(event)
+ event.recycle()
+ }
+ }
}
}
@@ -102,7 +119,7 @@ class GestureActivity: ToolbarActivity(), GestureViewCallback {
// Listen to events from ScreenReaderService
val filter = IntentFilter()
filter.addAction(Constants.SERVICE_ACTION)
- registerReceiver(receiver, filter)
+ registerBroadcastReceiver(receiver, filter)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
diff --git a/app/src/main/java/app/screenreader/tabs/gestures/GesturesFragment.kt b/app/src/main/java/app/screenreader/tabs/gestures/GesturesFragment.kt
index 2bebab9..9f12769 100644
--- a/app/src/main/java/app/screenreader/tabs/gestures/GesturesFragment.kt
+++ b/app/src/main/java/app/screenreader/tabs/gestures/GesturesFragment.kt
@@ -101,9 +101,18 @@ class GesturesFragment : ListFragment() {
}
private fun onGestureClicked(gesture: Gesture) {
+ val context = this.context ?: return
+
+ // If TalkBack is enabled, check if device supports gesture training with TalkBack
if (Accessibility.screenReader(context)) {
- context?.showDialog(R.string.service_talkback_enabled_title, R.string.service_talkback_enabled_message)
- return
+ if (!ScreenReaderService.supportsTalkBackCompatibility()) {
+ showTalkBackNotSupportedDialog()
+ return
+ }
+ if (!ScreenReaderService.isEnabled(context)) {
+ ScreenReaderService.enable(context, true)
+ return
+ }
}
startActivity(REQUEST_CODE_SINGLE) {
@@ -112,10 +121,7 @@ class GesturesFragment : ListFragment() {
}
private fun onPracticeClicked() {
- if (Accessibility.screenReader(context)) {
- context?.showDialog(R.string.service_talkback_enabled_title, R.string.service_talkback_enabled_message)
- return
- }
+ // Note: TalkBack compatibility is handled in startPractice() by enabling ScreenReaderService
AlertDialog.Builder(requireContext())
.setTitle(context?.getSpannable(R.string.gestures_practice_title))
@@ -136,6 +142,10 @@ class GesturesFragment : ListFragment() {
val context = this.context ?: return
if (Accessibility.screenReader(context)) {
+ if (!ScreenReaderService.supportsTalkBackCompatibility()) {
+ showTalkBackNotSupportedDialog()
+ return
+ }
if (!ScreenReaderService.isEnabled(context)) {
ScreenReaderService.enable(context, instructions)
return
@@ -150,6 +160,16 @@ class GesturesFragment : ListFragment() {
}
}
+ private fun showTalkBackNotSupportedDialog() {
+ AlertDialog.Builder(requireContext())
+ .setTitle(context?.getSpannable(R.string.talkback_not_supported_title))
+ .setMessage(context?.getSpannable(R.string.talkback_not_supported_message))
+ .setPositiveButton(context?.getSpannable(R.string.action_ok)) { _, _ ->
+ // Dismiss
+ }
+ .show()
+ }
+
companion object {
private const val REQUEST_CODE_SINGLE = 1
private const val REQUEST_CODE_MULTIPLE = 2
diff --git a/app/src/main/java/app/screenreader/views/actions/CopyActionView.kt b/app/src/main/java/app/screenreader/views/actions/CopyActionView.kt
index 6c68475..768fba4 100644
--- a/app/src/main/java/app/screenreader/views/actions/CopyActionView.kt
+++ b/app/src/main/java/app/screenreader/views/actions/CopyActionView.kt
@@ -4,7 +4,7 @@ import android.content.ClipboardManager
import android.content.Context
import app.screenreader.R
import app.screenreader.model.Action
-import kotlinx.android.synthetic.main.action_copy.view.*
+import app.screenreader.views.TrainingField
/**
* Created by Jan Jaap de Groot on 23/11/2020
@@ -24,7 +24,7 @@ class CopyActionView(context: Context): ActionView(
if (clip.itemCount > 0) {
val text = clip.getItemAt(0).text
- if (trainingField.text.toString().contains(text, false)) {
+ if (findViewById(R.id.trainingField).text.toString().contains(text, false)) {
correct()
} else {
incorrect(R.string.action_copy_incorrect)
diff --git a/app/src/main/java/app/screenreader/views/actions/PasteActionView.kt b/app/src/main/java/app/screenreader/views/actions/PasteActionView.kt
index 3f585fa..abd2b82 100644
--- a/app/src/main/java/app/screenreader/views/actions/PasteActionView.kt
+++ b/app/src/main/java/app/screenreader/views/actions/PasteActionView.kt
@@ -2,9 +2,9 @@ package app.screenreader.views.actions
import android.content.Context
import androidx.core.widget.addTextChangedListener
-import kotlinx.android.synthetic.main.action_paste.view.*
import app.screenreader.R
import app.screenreader.model.Action
+import app.screenreader.views.TrainingField
/**
* Created by Jan Jaap de Groot on 23/11/2020
@@ -17,7 +17,7 @@ class PasteActionView(context: Context) : ActionView(
) {
init {
- trainingField.addTextChangedListener(beforeTextChanged = { _, _, _, after ->
+ findViewById(R.id.trainingField).addTextChangedListener(beforeTextChanged = { _, _, _, after ->
if (after > 1) {
correct()
}
diff --git a/app/src/main/java/app/screenreader/views/actions/SelectionActionView.kt b/app/src/main/java/app/screenreader/views/actions/SelectionActionView.kt
index 63ba8a3..3fe1978 100644
--- a/app/src/main/java/app/screenreader/views/actions/SelectionActionView.kt
+++ b/app/src/main/java/app/screenreader/views/actions/SelectionActionView.kt
@@ -1,7 +1,6 @@
package app.screenreader.views.actions
import android.content.Context
-import kotlinx.android.synthetic.main.action_selection.view.*
import app.screenreader.R
import app.screenreader.model.Action
import app.screenreader.views.TrainingField
@@ -17,7 +16,7 @@ class SelectionActionView(context: Context) : ActionView(
), TrainingField.OnSelectionChangedListener {
init {
- trainingField.callback = this
+ findViewById(R.id.trainingField).callback = this
}
override fun onSelectionChanged(start: Int, end: Int) {
diff --git a/app/src/main/java/app/screenreader/views/gestures/SwipeGestureView.kt b/app/src/main/java/app/screenreader/views/gestures/SwipeGestureView.kt
index ad94b6c..1c8d631 100644
--- a/app/src/main/java/app/screenreader/views/gestures/SwipeGestureView.kt
+++ b/app/src/main/java/app/screenreader/views/gestures/SwipeGestureView.kt
@@ -9,7 +9,6 @@ import app.screenreader.extensions.isEnd
import app.screenreader.extensions.isStart
import app.screenreader.model.Direction
import app.screenreader.model.Gesture
-import app.screenreader.services.ScreenReaderService
/**
* Created by Jan Jaap de Groot on 15/10/2020
@@ -27,6 +26,7 @@ open class SwipeGestureView(
super.onTouchEvent(event)
if (event != null) {
+ Log.d(TAG, "onTouchEvent: action=${event.action}, pointerCount=${event.pointerCount}")
gestureDetector.onTouchEvent(event)
if (event.isStart()) {
@@ -42,14 +42,18 @@ open class SwipeGestureView(
}
override fun onAccessibilityGesture(gesture: Gesture) {
+ Log.d(TAG, "onAccessibilityGesture received: $gesture (expected: ${this.gesture})")
when {
this.gesture == gesture -> {
+ Log.d(TAG, "Gesture matches! Marking correct.")
correct()
}
gesture.directions.isNotEmpty() -> {
+ Log.d(TAG, "Gesture has directions but doesn't match, calling onSwipe")
onSwipe(gesture.directions)
}
else -> {
+ Log.d(TAG, "Unknown gesture, marking incorrect")
incorrect(R.string.gestures_feedback_swipe)
}
}
@@ -59,7 +63,7 @@ open class SwipeGestureView(
swiped = true
val fingers = directions.map { it.fingers }.average().toInt()
- Log.d(TAG, "onSwipe: ${directions.joinToString { it.toString() }}, fingers: $fingers")
+ Log.d(TAG, "onSwipe (touch-based): directions=${directions.joinToString { it.toString() }}, fingers=$fingers, expected=${gesture.fingers}")
when {
fingers != gesture.fingers -> {
@@ -82,32 +86,30 @@ open class SwipeGestureView(
private val THRESHOLD = 15
private var path = arrayListOf()
- override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+ override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
Log.d(TAG, "onScroll, distanceX: $distanceX, distanceY: $distanceY")
- // Determine direction
+ // Determine direction based on the DOMINANT axis (larger absolute movement)
var direction = Direction.UNKNOWN
- when {
- distanceX > THRESHOLD -> {
- direction = Direction.LEFT
- }
- distanceX < -THRESHOLD -> {
- direction = Direction.RIGHT
- }
- distanceY > THRESHOLD -> {
- direction = Direction.UP
- }
- distanceY < -THRESHOLD -> {
- direction = Direction.DOWN
+ val absX = kotlin.math.abs(distanceX)
+ val absY = kotlin.math.abs(distanceY)
+
+ // Only detect direction if movement exceeds threshold
+ // Prioritize the axis with larger movement to avoid detecting
+ // slight horizontal drift during vertical swipes (and vice versa)
+ if (absX > THRESHOLD || absY > THRESHOLD) {
+ if (absY >= absX) {
+ // Vertical movement is dominant
+ direction = if (distanceY > 0) Direction.UP else Direction.DOWN
+ } else {
+ // Horizontal movement is dominant
+ direction = if (distanceX > 0) Direction.LEFT else Direction.RIGHT
}
}
if (direction != Direction.UNKNOWN) {
// Determine amount of fingers
direction.fingers = e2.pointerCount ?: 1
- if (ScreenReaderService.isEnabled(context)) {
- direction.fingers++
- }
if (path.isEmpty()) {
// Add first direction
@@ -127,7 +129,7 @@ open class SwipeGestureView(
return super.onScroll(e1, e2, distanceX, distanceY)
}
- override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
+ override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
Log.d(TAG, "onFling, velocityX: $velocityX, velocityY: $velocityY")
if (path.isNotEmpty()) {
diff --git a/app/src/main/java/app/screenreader/views/gestures/TapGestureView.kt b/app/src/main/java/app/screenreader/views/gestures/TapGestureView.kt
index fc3c723..bc07688 100644
--- a/app/src/main/java/app/screenreader/views/gestures/TapGestureView.kt
+++ b/app/src/main/java/app/screenreader/views/gestures/TapGestureView.kt
@@ -7,7 +7,6 @@ import app.screenreader.extensions.isEnd
import app.screenreader.extensions.isStart
import app.screenreader.model.Gesture
import app.screenreader.model.Touch
-import app.screenreader.services.ScreenReaderService
import app.screenreader.R
/**
@@ -36,10 +35,6 @@ class TapGestureView(
hold = true
}
- if (ScreenReaderService.isEnabled(context)) {
- taps += 1
- }
-
when {
fingers != gesture.fingers -> {
diff --git a/app/src/main/java/app/screenreader/widgets/ListFragment.kt b/app/src/main/java/app/screenreader/widgets/ListFragment.kt
index 49cebe6..463acb6 100644
--- a/app/src/main/java/app/screenreader/widgets/ListFragment.kt
+++ b/app/src/main/java/app/screenreader/widgets/ListFragment.kt
@@ -3,12 +3,13 @@ package app.screenreader.widgets
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.RecyclerView
import app.screenreader.R
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
-import kotlinx.android.synthetic.main.view_list.*
abstract class ListFragment: BaseFragment() {
+ private val recyclerView get() = view?.findViewById(R.id.recyclerView)
override fun getLayoutId(): Int {
return R.layout.view_list
}
@@ -22,10 +23,10 @@ abstract class ListFragment: BaseFragment() {
super.onViewCreated(view, savedInstanceState)
adapter.items = items
- recyclerView.adapter = adapter
+ recyclerView?.adapter = adapter
if (decoration) {
- recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
+ recyclerView?.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
}
}
}
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index f24154b..b3d9402 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -379,4 +379,6 @@
Je moet de \'ScreenReader gebaren\'-service aanzetten zodat de ScreenReader app gebaren kan detecteren wanneer TalkBack geactiveerd is.\n\nVolg deze stappen:\n\n1. Navigeer naar de \'Activeren\' knop. Dubbeltik om de toegankelijkheidsinstellingen van je toestel te openen.\n2. Navigeer naar geïnstalleerde service met de naam \'ScreenReader gebaren\'. Dubbeltik om het instellingenscherm te openen.\n3. Navigeer naar de schakelaar. Dubbeltik om de service toe te staan.\n4. Navigeer naar \'Toestaan\'. Dubbeltik om de service te activeren.\n5. Je hebt het activatieproces succesvol doorlopen. De gebaren training wordt automatisch gestart.\n\nNavigeer nu naar de \'Activeren\' knop om het activatieproces te starten.
TalkBack staat aan
Wegens technische beperkingen kun je op dit moment geen gebaren oefenen wanneer TalkBack aan staat.
+ Android-versie niet ondersteund
+ Gebaren oefenen met TalkBack ingeschakeld vereist Android 12L (API 32) of hoger.\n\nJe apparaat draait op een oudere versie van Android. Schakel TalkBack uit om gebaren te oefenen, of werk je apparaat bij naar een nieuwere Android-versie.
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ae8d4ef..03e6d48 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -384,4 +384,6 @@
You need to enable the \'ScreenReader gestures\' service to allow the ScreenReader app to recognize gestures when TalkBack is activated.\n\nFollow these steps:\n\n1. Navigate to the \'Activate\' button. Double-tap to open the accessibility settings of your device.\n\n2. Navigate to installed service named \'ScreenReader gestures\'. Double tap to open the settings screen.\n\n3. Navigate to the switch. Double-tap to enable the service.\n\n4. Navigate to the \'Allow\' option. Double tap to activate the service.\n\n5. You have successfully completed the activation process. The gesture training should start automatically.
TalkBack is enabled
Due to technical limitations, you can\'t practice gestures while TalkBack is enabled at this time.
+ Android version not supported
+ Gesture training with TalkBack enabled requires Android 12L (API 32) or higher.\n\nYour device is running an older version of Android. Please disable TalkBack to practice gestures, or update your device to a newer Android version.
\ No newline at end of file
diff --git a/app/src/main/res/xml/accessibility_service_config.xml b/app/src/main/res/xml/accessibility_service_config.xml
new file mode 100644
index 0000000..2815cc6
--- /dev/null
+++ b/app/src/main/res/xml/accessibility_service_config.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/build.gradle b/build.gradle
index 02eec65..174d152 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '8.1.1' apply false
- id 'com.android.library' version '8.1.1' apply false
+ id 'com.android.application' version '8.13.0' apply false
+ id 'com.android.library' version '8.13.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
id 'com.google.gms.google-services' version '4.3.15' apply false
id 'com.google.firebase.crashlytics' version '2.9.9' apply false
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index e708b1c..d64cd49 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 218a1a0..37f853b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Wed Feb 16 13:49:22 CET 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 4f906e0..1aa94a4 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,99 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 107acd3..93e3f59 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
-@if "%DEBUG%" == "" @echo off
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
+if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal