Connect a DualSense to a Fire TV, open any Remote Play client, and the buttons are in the wrong places. L1 and R1 act like the triggers. The analog triggers don't pull. Options and Create do nothing at all. The interesting part is that it happens in every app the same way, which is usually a sign the app isn't the one to blame.
It isn't. The mapping is already wrong by the time any app sees it.
Android turns a gamepad's raw hardware scancodes into the key codes apps actually receive using a "key layout" file. You can watch both ends with adb shell getevent (raw events from the kernel) and the key codes the framework reports. On a Fire TV, the raw events are fine, but the key codes that come out the other side are shifted. So the controller is sending the right thing and the system is relabelling it.
Those key layout files live in /system/usr/keylayout/. Controllers the system recognizes get a device-specific one, named by USB vendor and product id. The DualSense shows up as vendor 054c, product 0ce6, so it gets Vendor_054c_Product_0ce6.kl. On recent Fire OS builds the mapping in that file is off. In practice:
AXIS_RX / AXIS_RY instead of AXIS_LTRIGGER / AXIS_RTRIGGER.Trusting the system mapping is the right default literally everywhere else, so any client that does the sensible thing inherits the scramble. There's nothing to fix in the protocol or the decoder. It's input, and it's upstream of the app.
akichi undoes the relabelling itself, but only where it has to. Two guards keep it from ever touching a setup that's already correct:
// The scramble is an Amazon Fire OS thing, so only ever remap on Amazon devices.
// On a phone, a non-Amazon Android TV box, or USB on desktop, this is a no-op.
private val brokenKeylayoutEnv = Build.MANUFACTURER.equals("Amazon", ignoreCase = true)
private fun isFireTvDualSense(device: InputDevice?): Boolean {
if (!controllerMapFix || !brokenKeylayoutEnv || device == null) return false
// Sony DualSense: vendor 0x054C, product 0x0CE6
return device.vendorId == 0x054C && device.productId == 0x0CE6
}
When both hold, akichi intercepts the key events, maps each one back to what it should have been, and reads the analog triggers off AXIS_RX / AXIS_RY. From the PS5's side the controller is just a normal DualSense again. The full corrective table and the axis handling are in the source if you want the exact mapping.
It defaults on for Fire TV and off everywhere else, so there's nothing to configure, and it can't break a controller that was already working.
The original Chiaki was archived back in 2021. chiaki-ng, the fork that's still maintained, is built around desktop and the Steam Deck, where this bug doesn't exist: Steam Input handles mapping, and a Steam Deck isn't a DualSense to begin with. Nobody was looking at the Fire TV case, so it just sat there. That's most of the reason akichi exists.
Source and the full mapping table: github.com/vargasvini/akichi. If you just want it working on your Fire TV, open the Downloader app and enter code 2031445.