Tell copilot to secure the Jetpack compose app
Video: Tell copilot to secure the Jetpack compose app by Taught by Celeste AI - AI Coding Coach
Tell Copilot to "Secure" a Jetpack Compose App
Vague prompts produce vague answers. Copilot's first move on "secure my app" is
WindowManager.LayoutParams.FLAG_SECURE— which only blocks screenshots. Real security is a much bigger conversation.
A meta-episode: what happens when you give Copilot a fuzzy goal like "secure this app." The answer reveals how Copilot reads context — and where its limitations show.
The Copilot prompt
// Make this Compose app secure (prevent screenshots)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Copilot generates:
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Prevent screenshots and screen recording
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
setContent {
MaterialTheme {
Surface {
Text("Secure App Content")
}
}
}
}
}
What FLAG_SECURE actually does
FLAG_SECURE is one specific Android security feature: it tells the system "don't include this window in screenshots, screen recordings, or non-secure displays."
Effects:
- Screenshots blocked. The user pressing power+volume-down gets a "couldn't capture screenshot" toast.
- Screen recording blocked. Apps trying to record show a black frame for your activity.
- Casting to non-secure displays blocked. Mirroring to a TV via Chromecast on most devices fails.
- Recent apps thumbnail blacked out. When the user opens the app switcher, your app's preview is just a black rectangle.
Banking apps and password managers commonly use this. It's not real security — a determined user with another camera can still capture the screen. But it raises the bar.
What "secure" actually means
"Secure" is not a single feature. The threats vary:
- Network MITM — TLS, certificate pinning, Network Security Config.
- Data at rest — EncryptedSharedPreferences, KeyStore-backed keys, full-disk encryption.
- Reverse engineering — ProGuard/R8 obfuscation, Play Integrity, root/Magisk detection.
- Tampering — APK signing, signature checks at runtime.
- Authentication — biometric, PIN, OAuth tokens with refresh.
- Social engineering — overlay attacks, screen recording (FLAG_SECURE), accessibility-service abuse.
Copilot reads "secure" as the most common meaning in the training data, which for Android is often FLAG_SECURE. It's a starting point, not an answer.
Stacking the standard precautions
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Block screenshots and recording
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
setContent { /* ... */ }
}
}
Note addFlags(FLAG) is more correct than setFlags(FLAG, FLAG) — addFlags ORs into existing flags; setFlags(value, mask) replaces just the masked bits. For a single flag, addFlags is clearer.
EncryptedSharedPreferences
For storing secrets locally:
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPrefs = EncryptedSharedPreferences.create(
context,
"secret_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
sharedPrefs.edit().putString("token", userToken).apply()
val token = sharedPrefs.getString("token", null)
Encrypts both keys and values, with the master key stored in Android Keystore (hardware-backed on supporting devices).
For data files: EncryptedFile from the same androidx.security.crypto package.
Network Security Config
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set>
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
</pin-set>
</domain-config>
</network-security-config>
Pin specific certificates so even a compromised CA can't MITM your traffic. Reference from AndroidManifest.xml:
<application
android:networkSecurityConfig="@xml/network_security_config">
Biometric authentication
import androidx.biometric.BiometricPrompt
val biometricPrompt = BiometricPrompt(this, executor, callback)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Use your fingerprint")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
Use for "unlock the app" or "confirm sensitive action" prompts.
Anti-tampering: signature check
fun isOurSignature(context: Context): Boolean {
val packageInfo = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
val signatures = packageInfo.signingInfo.apkContentsSigners
// Compare against known good fingerprint
return signatures.any { it.toCharsString() == EXPECTED_SIGNATURE }
}
Dies on rooted/repackaged apps. Doesn't stop a determined attacker — but the bar is higher.
What Copilot won't tell you
- Threat modeling. What are you actually defending against? Casual users? Insider screenshots? State-level adversaries? The answer determines the cost of your security.
- Trade-offs. FLAG_SECURE breaks legitimate accessibility tools. EncryptedSharedPreferences are slower than plain ones. Pinning breaks if you renew certs and forget to update the app.
- The cost of false security. Adding
FLAG_SECUREand calling the app "secure" might lull stakeholders into skipping the actual hard work (TLS, pinning, key management, server-side checks).
Copilot can scaffold the implementation, not the threat model. Always sketch the model before reaching for the API.
Common mistakes
FLAG_SECURE as security theater. It blocks screenshots, not data exfiltration, network sniffing, or rooted-device attacks.
Storing secrets in plain SharedPreferences. Anyone with adb access reads them. Use EncryptedSharedPreferences or the Android Keystore.
Trusting client-side checks alone. Server must verify everything. Client checks add friction; they don't add security.
Not pinning TLS certs for high-value endpoints. A compromised CA can MITM your traffic without pinning.
Ignoring obfuscation. Reverse-engineering an unobfuscated APK takes minutes. Enable R8 minification and shrinking.
What's next
Episode 21: Person detail Composable with just a username. Back to UI fundamentals — a small Compose screen that takes a string and lays out a card.
Recap
"Secure" is a multi-dimensional goal. FLAG_SECURE is one tool — blocks screenshots and recording. Real security requires threat modeling first: encrypted storage (EncryptedSharedPreferences), TLS pinning (Network Security Config), authentication (BiometricPrompt), anti-tampering (signature checks), obfuscation (R8). Copilot scaffolds the implementation; you write the threat model.
Next episode: person detail Composable.