Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,11 @@ class ChatGptKeyAccessibilityService : AccessibilityService() {
if (isClickableActionNode(node, profile)) node else clickableAncestor
if (clickableTarget != null &&
isIdentityBearingActionNode(node, profile) &&
profile.hasSendIdentity(node.contentDescription, node.viewIdResourceName) &&
profile.hasSendIdentity(
node.contentDescription,
node.viewIdResourceName,
node.actionList.map { it.label }
) &&
matches.none { it == clickableTarget }
) {
matches += clickableTarget
Expand Down Expand Up @@ -444,8 +448,9 @@ class ChatGptKeyAccessibilityService : AccessibilityService() {
node.isEditable
) return false

val className = node.className?.toString()
return node.isClickable || className == CLASS_BUTTON || className == CLASS_VIEW
// Compose and React Native may expose an action's exact semantic label on
// a non-clickable icon child while ACTION_CLICK lives on its ancestor.
return true
}

private fun supportsAction(node: AccessibilityNodeInfo, action: Int): Boolean =
Expand Down Expand Up @@ -490,9 +495,7 @@ class ChatGptKeyAccessibilityService : AccessibilityService() {
)

companion object {
private const val CLASS_BUTTON = "android.widget.Button"
private const val CLASS_VIEW = "android.view.View"
private const val MAX_COMPOSER_ANCESTOR_LEVELS = 5
private const val MAX_COMPOSER_ANCESTOR_LEVELS = 8
private const val MAX_COMPOSER_ANCHOR_ANCESTORS = 3
private const val SEND_POLL_INTERVAL_MS = 100L
private const val SEND_CONFIRM_TIMEOUT_MS = 3_000L
Expand Down
23 changes: 17 additions & 6 deletions app/src/main/java/com/ctech/enter2send/SupportedAppProfile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ data class SupportedAppProfile(

fun hasSendIdentity(
contentDescription: CharSequence?,
viewIdResourceName: String?
viewIdResourceName: String?,
actionLabels: Iterable<CharSequence?> = emptyList()
): Boolean {
val description = contentDescription?.toString()?.trim()
if (description != null && sendDescriptions.any {
it.equals(description, ignoreCase = true)
}
if (matchesSendDescription(contentDescription) ||
actionLabels.any(::matchesSendDescription)
) {
return true
}
Expand All @@ -31,6 +30,11 @@ data class SupportedAppProfile(
return sendViewIdSuffixes.any(viewId::endsWith)
}

private fun matchesSendDescription(description: CharSequence?): Boolean {
val normalized = description?.toString()?.trim() ?: return false
return sendDescriptions.any { it.equals(normalized, ignoreCase = true) }
}

fun hasRequiredWindowIdentity(contentDescription: CharSequence?): Boolean {
val description = contentDescription?.toString()?.trim() ?: return false
return requiredWindowIdentities.any {
Expand All @@ -46,7 +50,14 @@ object SupportedAppProfiles {
packageName = "com.openai.chatgpt",
preferenceKey = "app_chatgpt_enabled",
enabledByDefault = true,
sendDescriptions = setOf("Send", "Send message"),
sendDescriptions = setOf(
"Send",
"Send message",
"Send prompt",
"전송",
"메시지 보내기",
"프롬프트 보내기"
),
sendViewIdSuffixes = setOf(
"/send",
"/send_button",
Expand Down
20 changes: 20 additions & 0 deletions app/src/test/java/com/ctech/enter2send/SupportedAppProfilesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ class SupportedAppProfilesTest {
assertFalse(profile.hasSendIdentity("Resend", null))
}

@Test
fun matchesCurrentChatGptSendSemantics() {
val profile = SupportedAppProfiles.chatGpt

assertTrue(profile.hasSendIdentity("Send prompt", null))
assertTrue(profile.hasSendIdentity("전송", null))
assertTrue(profile.hasSendIdentity("메시지 보내기", null))
assertTrue(profile.hasSendIdentity("프롬프트 보내기", null))
assertFalse(profile.hasSendIdentity("Resend prompt", null))
}

@Test
fun matchesSendSemanticsExposedOnlyAsAnActionLabel() {
val profile = SupportedAppProfiles.chatGpt

assertTrue(profile.hasSendIdentity(null, null, listOf("Send prompt")))
assertTrue(profile.hasSendIdentity(null, null, listOf(null, "메시지 보내기")))
assertFalse(profile.hasSendIdentity(null, null, listOf("Resend prompt")))
}

@Test
fun matchesOnlyKnownSendViewIdSuffixes() {
val profile = SupportedAppProfiles.messenger
Expand Down