Skip to content

Instantly share code, notes, and snippets.

@omkar-tenkale
Last active February 1, 2026 09:59
Show Gist options
  • Select an option

  • Save omkar-tenkale/0f09c1050276927b918fe35651f85840 to your computer and use it in GitHub Desktop.

Select an option

Save omkar-tenkale/0f09c1050276927b918fe35651f85840 to your computer and use it in GitHub Desktop.
Android Dev Quest Answers

Solutions for app Android Dev Quest by Richard Zadorozny

https://play.google.com/store/apps/details?id=quest.androiddev

[This document is created in haste, some solutions might not be complete or insufficient]

Quiz 1 - show layout bounds

Quiz 2 - running services

Quiz 3 - debug gpu overdraw

Quiz 4 - show hardware layer updates

Quiz 5 - simulate secondary display

Quiz 6 - Pointer location

Quiz 7 - show hardware layer updates + show layout bounds + simulate secondary display

Quiz 8 - Mock location - locaedit app and Marion Island Meteorological Station

Quiz 9 - Answer in logcat with radio buffer

Quiz 10 - nc localhost [portnumber]

Quiz 11- adb shell am startservice -n quest.androiddev/.morgannode adb shell am broadcast -a quest.androiddev.READY_TO_WORK -p quest.androiddev adb shell am broadcast -a quest.androiddev.READY_TO_WORK -p quest.androiddev -c android.intent.category.INFO -t message/global

Quiz 12 - adb shell am crash quest.androiddev

Quiz 13 - adb shell input tap x y adb shell input swipe x1 y1 x2 y2

Quiz 14- Read the file at path content query --url content://.. content call .. url .. method ..arg Echo result to verify.xxx.txt path seen on swipe up

Quiz 15- adb shell am broadcast -a HERE_KITTY_KITTY adb shell input tap ... adb shell input swipe ...

Quiz 16: Register New Agent Approach: Instrumented test that launches the app and clicks the "Register" button. . Install APK with adb install -g for granting dangerous permission {necessary}

private const val QUEST_PACKAGE = "quest.androiddev"
private const val REGISTER_ACTION = "quest.androiddev.action.AGENT_REGISTER"
private const val REGISTER_PERMISSION = "quest.androiddev.permission.AGENT_INSTRUMENTS"
private const val USERNAME = "your_username"

@Test
fun registerNewAgent() {
    val context = InstrumentationRegistry.getInstrumentation().targetContext
    
    val intent = Intent(REGISTER_ACTION).apply {
        `package` = QUEST_PACKAGE
        putExtra("EXTRA_USERNAME", USERNAME)
    }
    context.sendBroadcast(intent)
}

Quiz 17: Invisible Node + Progressions Note: below code might not be fully correct the solution has progressions eg fist you need to click the code then call custom action on it like galvanize etc.

// Custom accessibility action IDs from the quest app
private const val ACTION_GALVANIZE = 2131296273
private const val ACTION_SET_PROGRESS = 16908349  // android.R.id.accessibilityActionSetProgress

@Test
fun invisibleNode_allSteps() {
    val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
    
    // Step 1: Click
    var node = findInvisibleNode(uiAutomation) ?: error("Invisible node not found")
    node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
    Thread.sleep(1000)
    
    // Step 2: Galvanize (re-fetch node)
    node = findInvisibleNode(uiAutomation) ?: error("Invisible node not found")
    node.performAction(ACTION_GALVANIZE)
    Thread.sleep(1000)
    
    // Step 3: Set Progress to 100 (re-fetch node)
    node = findInvisibleNode(uiAutomation) ?: error("Invisible node not found")
    val args = Bundle().apply {
        putFloat(AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE, 100f)
    }
    node.performAction(ACTION_SET_PROGRESS, args)
}

private fun findInvisibleNode(uiAutomation: UiAutomation): AccessibilityNodeInfo? {
    uiAutomation.serviceInfo = AccessibilityServiceInfo().apply {
        flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS or
            AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
    }
    Thread.sleep(500)
    
    val root = uiAutomation.rootInActiveWindow ?: return null
    val queue = ArrayDeque<AccessibilityNodeInfo>().apply { add(root) }
    
    while (queue.isNotEmpty()) {
        val node = queue.removeFirst()
        if (node.contentDescription?.toString()?.contains("Invisible", ignoreCase = true) == true) {
            return node
        }
        for (i in 0 until node.childCount) {
            node.getChild(i)?.let(queue::add)
        }
    }
    return null
}

Quiz 18: AIDL + Unlock Slots Note: Call openSlot(20) → triggers "error 28" → call openSlot(28) → reveals unlock_payload in getScreenData() → call submit(unlock_payload) Prerequisites: AIDL file at app/src/main/aidl/com/qualityserviceagents/ICoreAccessService.aidl buildFeatures { aidl = true } in build.gradle.kts in AndroidManifest.xml

@Test
fun coreAccessService_solve() {
    val context = InstrumentationRegistry.getInstrumentation().targetContext
    val latch = CountDownLatch(1)
    var service: ICoreAccessService? = null
    
    val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            service = ICoreAccessService.Stub.asInterface(binder)
            latch.countDown()
        }
        override fun onServiceDisconnected(name: ComponentName?) {}
    }
    
    val intent = Intent().apply {
        component = ComponentName("quest.androiddev", 
            "com.qualityserviceagents.CoreAccessService")
    }

    context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
    latch.await(10, TimeUnit.SECONDS)
    
    // Step 1: Open slot 20 to trigger error 28
    service!!.openSlot(20)
    
    // Step 2: Open slot 28 to reveal unlock_payload
    service!!.openSlot(28)
    
    // Step 3: Get screen data with unlock_payload
    val screenData = service!!.screenData
    val unlockPayload = screenData.getString("unlock_payload")
    
    // Step 4: Submit unlock_payload
    service!!.submit(unlockPayload)
    
    context.unbindService(connection)
}

Quiz 19: Agent Chat - Receive Code via Callback Note: Register ICoreAccessCallback with the service. When onResult(payload) fires, submit the payload back.

 @Test
fun solveAgentChat() {
    val context = InstrumentationRegistry.getInstrumentation().targetContext
    val latch = CountDownLatch(1)
    var service: ICoreAccessService? = null
    
    val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            service = ICoreAccessService.Stub.asInterface(binder)
            latch.countDown()
        }
        override fun onServiceDisconnected(name: ComponentName?) {}
    }
    
    val intent = Intent().apply {
        component = ComponentName("quest.androiddev", 
            "com.qualityserviceagents.CoreAccessService")
    }
    context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
    latch.await(10, TimeUnit.SECONDS)
    
    val codeLatch = CountDownLatch(1)
    var receivedCode: String? = null
    
    val callback = object : ICoreAccessCallback.Stub() {
        override fun onResult(payload: String?) {
            receivedCode = payload
            codeLatch.countDown()
        }
    }

    service?.addCallback(callback)
    
    // Wait for code (may take time during chat)
    codeLatch.await(30, TimeUnit.SECONDS)
    
    // Submit received code
    receivedCode?.let { service?.submit(it) }
    
    context.unbindService(connection)
}

Quiz 20: Rocket Fire (Typing Defense)

Note: this has some complications some events need multiple keys eg "ME" "GO" eg update solution accordingly

@Test
fun solve() {
    val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
    val letterQueue = ConcurrentLinkedQueue<Char>()
    
    val listener = UiAutomation.OnAccessibilityEventListener { event ->
        if (event?.packageName == "quest.androiddev" &&
            event.className?.contains("TypingWizard") == true &&
            event.contentDescription == "new letters") {
            
            // IMPORTANT: iterate all characters, not just single-char events!
            event.text.firstOrNull()?.toString()?.forEach { char ->
                if (char.isLetter()) letterQueue.add(char.uppercaseChar())
            }
        }
    }
    uiAutomation.setOnAccessibilityEventListener(listener)
    
    // Main typing loop
    val startTime = System.currentTimeMillis()
    while (System.currentTimeMillis() - startTime < 300_000) {
        val letter = letterQueue.poll() ?: continue
        val keyCode = KeyEvent.KEYCODE_A + (letter - 'A')
        val now = SystemClock.uptimeMillis()
        val down = KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0)
        val up = KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0)
        uiAutomation.injectInputEvent(down, true)
        uiAutomation.injectInputEvent(up, true)
    }
}

Quiz 21: Boss Quiz (All Combined) https://youtu.be/rr-CA1I_YMk?si=g9Z2BZjjiD4E_zsD

Note: All three tasks run simultaneously: Typing falling letters (continuous) Receiving AIDL callback codes and submitting them Finding "morgan.node" and performing "transcend" action

@Test
fun solveWithCallback() {
    val instrumentation = InstrumentationRegistry.getInstrumentation()
    val uiAutomation = instrumentation.uiAutomation
    val context = instrumentation.targetContext
    
    // ========== AIDL SERVICE SETUP ==========
    val serviceLatch = CountDownLatch(1)
    var service: ICoreAccessService? = null
    val callbackCodes = ConcurrentLinkedQueue<String>()
    
    val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            service = ICoreAccessService.Stub.asInterface(binder)
            serviceLatch.countDown()
        }
        override fun onServiceDisconnected(name: ComponentName?) { service = null }
    }
    
    val serviceIntent = Intent().apply {
        component = ComponentName("quest.androiddev", "com.qualityserviceagents.CoreAccessService")
    }
    context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)
    serviceLatch.await(10, TimeUnit.SECONDS)
    
    // Register callback
    val callback = object : ICoreAccessCallback.Stub() {
        override fun onResult(payload: String?) {
            if (!payload.isNullOrEmpty()) callbackCodes.add(payload)
        }
    }
    service?.addCallback(callback)
    
    // ========== ACCESSIBILITY EVENT SETUP ==========
    val letterQueue = ConcurrentLinkedQueue<Char>()
    val morganTranscended = AtomicBoolean(false)

    
    val listener = UiAutomation.OnAccessibilityEventListener { event ->
        if (event?.packageName == "quest.androiddev" &&
            event.className?.contains("TypingWizard") == true &&
            event.contentDescription == "new letters") {
            event.text.firstOrNull()?.toString()?.forEach { char ->
                if (char.isLetter()) letterQueue.add(char.uppercaseChar())
            }
        }
    }
    uiAutomation.setOnAccessibilityEventListener(listener)
    
    // ========== MAIN LOOP ==========
    val startTime = System.currentTimeMillis()
    var lastMorganScan = 0L
    
    while (System.currentTimeMillis() - startTime < 300_000) {
        // 1. Process AIDL callback codes
        callbackCodes.poll()?.let { code ->
            service?.submit(code)
        }
        
        // 2. Type letters
        letterQueue.poll()?.let { letter ->
            val keyCode = KeyEvent.KEYCODE_A + (letter - 'A')
            val now = SystemClock.uptimeMillis()
            uiAutomation.injectInputEvent(KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0), true)
            uiAutomation.injectInputEvent(KeyEvent(now, now, KeyEvent.ACTION_UP, keyCode, 0), true)
        }
        

        // 3. Scan for Morgan Node every 2 seconds
        if (!morganTranscended.get() && System.currentTimeMillis() - lastMorganScan > 2000) {
            lastMorganScan = System.currentTimeMillis()
            findMorganNode(uiAutomation)?.let { morganNode ->
                // Find and perform "transcend" action
                morganNode.actionList?.find { 
                    it.label?.toString()?.contains("transcend", ignoreCase = true) == true 
                }?.let { action ->
                    if (morganNode.performAction(action.id)) morganTranscended.set(true)
                } ?: run {
                    // Fallback: try all custom actions
                    morganNode.actionList?.filter { it.id > 0x10000 }?.forEach { action ->
                        morganNode.performAction(action.id)
                    }
                }
                morganNode.recycle()
            }
        }
    }
    
    context.unbindService(connection)
}

private fun findMorganNode(uiAutomation: UiAutomation): AccessibilityNodeInfo? {
    val root = uiAutomation.rootInActiveWindow ?: return null
    val queue = ArrayDeque<AccessibilityNodeInfo>().apply { add(root) }
    
    while (queue.isNotEmpty()) {
        val node = queue.removeFirst()
        val desc = node.contentDescription?.toString() ?: ""
        if (desc.contains("morgan", ignoreCase = true)) return node
        for (i in 0 until node.childCount) {
            node.getChild(i)?.let(queue::add)
        }
    }
    return null
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment