> For the complete documentation index, see [llms.txt](https://docs.talsec.app/appsec-articles/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.talsec.app/appsec-articles/glossary/secure-input-protection.md).

# Secure Input Protection

A PIN entry screen is a high-value target: attackers try to read it via screen capture, steal it through tap-jacking overlays, scrape it with a malicious accessibility service, or capture keystrokes through a compromised third-party keyboard (IME). As a general prerequisite, RASP+ **Overlay Detection**, **Accessibility Misuse Detection**, and **Malware Detection** should be running app-wide — they catch the device- and app-level threats that no per-screen setting can see. Android itself does not ship a dedicated "secure keyboard" component, but you can build the equivalent for the screen itself using standard platform primitives.

<figure><img src="/files/RP3QA0oE8VInBMELx6Dt" alt=""><figcaption></figcaption></figure>

There are two supported approaches, in order of strength:

1. **Custom in-app PIN keypad (recommended for PINs).** The app draws its own numeric buttons and writes straight into an in-memory buffer. Because no system IME is involved, a keylogging or replacement keyboard has nothing to intercept — the keystrokes never leave your process. This is the closest Android equivalent to a "secure keyboard" for short numeric secrets.
2. **System keyboard, hardened.** If you must use a normal text field, force the sensitive `numberPassword` input type (which suppresses personalized learning and suggestions), disable autofill and the clipboard, and — for the highest assurance — restrict input to the pre-installed system IME rather than any installed third-party keyboard.

Both approaches sit on top of the same screen-level hardening: `FLAG_SECURE` to block screenshots, screen recording, and the recents thumbnail, and obscured-touch filtering to defeat tap-jacking overlays.

## Custom in-app PIN keypad (Compose)

```kotlin
@Composable
fun SecurePinKeypad(onPinComplete: (String) -> Unit) {
    val view = LocalView.current
    DisposableEffect(Unit) {
        val window = (view.context as Activity).window
        window.setFlags(FLAG_SECURE, FLAG_SECURE)
        onDispose { window.clearFlags(FLAG_SECURE) }
    }

    // PIN lives only in memory — never touches an IME, autofill, or the clipboard.
    var pin by remember { mutableStateOf("") }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        // Masked dots reflecting the current length.
        Row(Modifier.padding(24.dp)) {
            repeat(4) { i ->
                Box(
                    Modifier.size(16.dp).padding(4.dp)
                        .background(
                            if (i < pin.length) Color.Black else Color.LightGray,
                            CircleShape
                        )
                )
            }
        }

        val keys = listOf("1","2","3","4","5","6","7","8","9","","0","⌫")
        keys.chunked(3).forEach { row ->
            Row {
                row.forEach { key ->
                    Button(
                        onClick = {
                            when (key) {
                                "" -> {}
                                "⌫" -> pin = pin.dropLast(1)
                                else -> if (pin.length < 4) {
                                    pin += key
                                    if (pin.length == 4) onPinComplete(pin)
                                }
                            }
                        },
                        enabled = key.isNotEmpty(),
                        modifier = Modifier.size(72.dp).padding(4.dp)
                    ) { Text(key, fontSize = 20.sp) }
                }
            }
        }
    }
}
```

## Hardened system keyboard

### Jetpack Compose

```kotlin
@Composable
fun SecurePinEntry() {
    val view = LocalView.current

    // FLAG_SECURE: no screenshots, no screen recording, blanked in recents.
    DisposableEffect(Unit) {
        val window = (view.context as Activity).window
        window.setFlags(
            WindowManager.LayoutParams.FLAG_SECURE,
            WindowManager.LayoutParams.FLAG_SECURE
        )
        onDispose { window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) }
    }

    var pin by remember { mutableStateOf("") }

    OutlinedTextField(
        value = pin,
        onValueChange = { if (it.length <= 6 && it.all(Char::isDigit)) pin = it },
        label = { Text("PIN") },
        singleLine = true,
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(
            // Password keyboard type is treated as sensitive: the IME keeps
            // entered digits out of personalized-learning/suggestions.
            keyboardType = KeyboardType.NumberPassword,
            imeAction = ImeAction.Done
        ),
        // pointerInteropFilter requires @OptIn(ExperimentalComposeUiApi::class).
        // Reject taps that arrive while another window sits on top (tap-jacking).
        modifier = Modifier.pointerInteropFilter { event ->
            val obscured = event.flags and
                (MotionEvent.FLAG_WINDOW_IS_OBSCURED or
                    MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED)
            obscured != 0 // true = consume & ignore the event
        }
    )
}
```

In Compose, set `importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO` on the host `AndroidView`/root, or disable autofill for the Activity, since the `TextField` itself does not expose an autofill flag.

### Classic XML / View

Layout (`res/layout/activity_pin.xml`):

```xml
<EditText
    android:id="@+id/pinField"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="numberPassword"
    android:maxLength="6"
    android:filterTouchesWhenObscured="true"
    android:importantForAutofill="no"
    android:imeOptions="flagNoPersonalizedLearning" />
```

Activity:

```kotlin
class PinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // FLAG_SECURE must be set before setContentView to also cover the recents thumbnail.
        window.setFlags(
            WindowManager.LayoutParams.FLAG_SECURE,
            WindowManager.LayoutParams.FLAG_SECURE
        )
        setContentView(R.layout.activity_pin)

        val pinField = findViewById<EditText>(R.id.pinField)

        // Disable copy/paste/selection so the PIN can't reach the clipboard.
        pinField.customSelectionActionModeCallback = object : ActionMode.Callback {
            override fun onCreateActionMode(mode: ActionMode?, menu: Menu?) = false
            override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?) = false
            override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = false
            override fun onDestroyActionMode(mode: ActionMode?) {}
        }
        pinField.isLongClickable = false

        // Belt-and-braces: also enforce obscured-touch rejection in code.
        pinField.filterTouchesWhenObscured = true
    }
}
```

`filterTouchesWhenObscured` and `FLAG_SECURE` defend the screen locally, but they cannot detect a malicious accessibility service or malware already resident on the device. Keep Talsec **Accessibility Misuse Detection**, **Overlay Detection**, and **Malware Detection** enabled app-wide so those threats are caught before the user ever reaches this screen.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.talsec.app/appsec-articles/glossary/secure-input-protection.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
