fix(app): secrets are kept out of screenshots and the recent apps

The recovery phrase, recovery codes and an authenticator's QR code and
key now show inside a SecretScreen: on Android it holds FLAG_SECURE while
it shows (through a channel each app's MainActivity answers, counting
holds, as several may show at once; ScreenGuard.kt), so screenshots,
screen recordings and the recents view don't get them; on iOS, which has
no such flag, it covers them while the app isn't in front, so the app
switcher's picture doesn't hold them (Dart-side, not checked on a device).
Desktops do neither: a window loses focus whenever its person turns to a
password manager. Todo's debug APK builds with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 15:09:02 +02:00
commit f3c3f7fb51
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 509 additions and 180 deletions

View file

@ -19,6 +19,7 @@ class MainActivity : FlutterActivity() {
super.configureFlutterEngine(flutterEngine)
val messenger = flutterEngine.dartExecutor.binaryMessenger
PomodoroService.appBack()
ScreenGuard.attach(this, messenger)
// A question searched with the device's own web search (its default
// browser or search app answers ACTION_WEB_SEARCH); false where none does,
// and the app opens a search engine's address instead.

View file

@ -0,0 +1,36 @@
package app.halkyon.learn
import android.app.Activity
import android.view.WindowManager
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
/**
* Keeps the window's content out of screenshots, screen recordings and the
* recents view (FLAG_SECURE) while halkyon_app shows something secret: the
* recovery phrase, recovery codes, an authenticator's key. Its SecretScreen
* holds as each shows and releases as it goes; counted, as several may show
* at once.
*/
object ScreenGuard {
fun attach(activity: Activity, messenger: BinaryMessenger) {
var holds = 0
MethodChannel(messenger, "app.halkyon/screen_guard").setMethodCallHandler { call, result ->
when (call.method) {
"hold" -> {
holds++
activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
result.success(null)
}
"release" -> {
holds = maxOf(0, holds - 1)
if (holds == 0) {
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
result.success(null)
}
else -> result.notImplemented()
}
}
}
}