From 5c6946aa81df33a5acd3c727e4445a70c6b6a8fe Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 20:15:52 +0000 Subject: [PATCH 01/10] Add %app% and %package% filename placeholders for foreground app info Allow users to include the foreground application name or package name in screenshot filenames. The accessibility service now tracks the foreground package unconditionally (not only when package filter is enabled), and formatFileName resolves the new placeholders via PackageManager.getApplicationLabel(). https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM --- .../ScreenshotAccessibilityService.kt | 13 +++++-- .../github/cvzi/screenshottile/utils/Utils.kt | 34 +++++++++++++++++++ app/src/main/res/values-en/strings.xml | 4 ++- app/src/main/res/values-ru/strings.xml | 4 ++- app/src/main/res/values/strings.xml | 4 ++- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index 6e536822c..bc0577230 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -174,6 +174,12 @@ class ScreenshotAccessibilityService : AccessibilityService() { private var packageFilterMode = PackageNameFilterMode.BLACKLIST private val packageFilterNameList: HashSet = HashSet() private var lastPackageName: CharSequence = "" + + /** + * Get the package name of the last foreground application + */ + fun getForegroundPackageName(): String = lastPackageName.toString() + private var prefManager = App.getInstance().prefManager private var lastClickTime = 0L private val doubleClickThreshold = 300L // milliseconds @@ -1035,13 +1041,16 @@ class ScreenshotAccessibilityService : AccessibilityService() { } override fun onAccessibilityEvent(event: AccessibilityEvent) { - if (packageFilterEnabled && - event.isFullScreen && + if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.packageName != lastPackageName ) { lastPackageName = event.packageName + if (!packageFilterEnabled) { + return + } + if (packageFilterTempForceShow) { // Temporary show floating button despite filter until the next app change but at least 60s if (System.currentTimeMillis() - packageFilterTempOverrideTime < 60000) { diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index 7af21db61..eedf785a8 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -451,9 +451,43 @@ fun formatFileName(fileNamePattern: String, date: Date): String { while (fileName.contains("%random%")) { fileName = fileName.replaceFirst("%random%", UUID.randomUUID().toString()) } + if (fileName.contains("%app%") || fileName.contains("%package%")) { + val service = ScreenshotAccessibilityService.instance + val packageName = service?.getForegroundPackageName() ?: "" + fileName = fileName.replace("%package%", sanitizeFilename(packageName)) + if (fileName.contains("%app%")) { + val appLabel = if (packageName.isNotEmpty()) { + try { + val context = App.getInstance() + val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + context.packageManager.getApplicationInfo(packageName, 0) + } + context.packageManager.getApplicationLabel(appInfo).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageName + } + } else { + "" + } + fileName = fileName.replace("%app%", sanitizeFilename(appLabel)) + } + } return fileName } +/** + * Remove characters that are not allowed in filenames + */ +private fun sanitizeFilename(name: String): String { + return name.replace(Regex("[\\\\/:*?\"<>|]"), "_") +} + /** * Cut cutOutRect from bitmap and return new bitmap, * return old bitmap if cutOutRect is null or malformed diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 7806bb73b..4f7dea946 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -62,7 +62,9 @@ +%randint% - a random number\n +%app% - name of the foreground app\n +%package% - package name of the foreground app]]> Tile action Tile long-press action Floating button action diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 426ca7e04..a87c58c57 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -62,7 +62,9 @@ +%randint% - случайное число\n +%app% - название приложения на переднем плане\n +%package% - имя пакета приложения на переднем плане]]> Действие плитки Действие при длительном нажатии на плитку Действие плавающей кнопки diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69558f70a..b5e90d853 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,9 @@ +%randint% - a random number\n +%app% - name of the foreground app\n +%package% - package name of the foreground app]]> ‌Tile action ‌Tile long-press action ‌Floating button action From 4b53e350b8401a6ca9304ecca40f9757810d0248 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 20:15:52 +0000 Subject: [PATCH 02/10] Add %app% and %package% filename placeholders for foreground app info Allow users to include the foreground application name or package name in screenshot filenames. The accessibility service now tracks the foreground package unconditionally (not only when package filter is enabled), and formatFileName resolves the new placeholders via PackageManager.getApplicationLabel(). https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM --- .../ScreenshotAccessibilityService.kt | 13 +++++-- .../github/cvzi/screenshottile/utils/Utils.kt | 34 +++++++++++++++++++ app/src/main/res/values-en/strings.xml | 4 ++- app/src/main/res/values-ru/strings.xml | 4 ++- app/src/main/res/values/strings.xml | 4 ++- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index 6e536822c..bc0577230 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -174,6 +174,12 @@ class ScreenshotAccessibilityService : AccessibilityService() { private var packageFilterMode = PackageNameFilterMode.BLACKLIST private val packageFilterNameList: HashSet = HashSet() private var lastPackageName: CharSequence = "" + + /** + * Get the package name of the last foreground application + */ + fun getForegroundPackageName(): String = lastPackageName.toString() + private var prefManager = App.getInstance().prefManager private var lastClickTime = 0L private val doubleClickThreshold = 300L // milliseconds @@ -1035,13 +1041,16 @@ class ScreenshotAccessibilityService : AccessibilityService() { } override fun onAccessibilityEvent(event: AccessibilityEvent) { - if (packageFilterEnabled && - event.isFullScreen && + if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.packageName != lastPackageName ) { lastPackageName = event.packageName + if (!packageFilterEnabled) { + return + } + if (packageFilterTempForceShow) { // Temporary show floating button despite filter until the next app change but at least 60s if (System.currentTimeMillis() - packageFilterTempOverrideTime < 60000) { diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index 7af21db61..eedf785a8 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -451,9 +451,43 @@ fun formatFileName(fileNamePattern: String, date: Date): String { while (fileName.contains("%random%")) { fileName = fileName.replaceFirst("%random%", UUID.randomUUID().toString()) } + if (fileName.contains("%app%") || fileName.contains("%package%")) { + val service = ScreenshotAccessibilityService.instance + val packageName = service?.getForegroundPackageName() ?: "" + fileName = fileName.replace("%package%", sanitizeFilename(packageName)) + if (fileName.contains("%app%")) { + val appLabel = if (packageName.isNotEmpty()) { + try { + val context = App.getInstance() + val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + context.packageManager.getApplicationInfo(packageName, 0) + } + context.packageManager.getApplicationLabel(appInfo).toString() + } catch (e: PackageManager.NameNotFoundException) { + packageName + } + } else { + "" + } + fileName = fileName.replace("%app%", sanitizeFilename(appLabel)) + } + } return fileName } +/** + * Remove characters that are not allowed in filenames + */ +private fun sanitizeFilename(name: String): String { + return name.replace(Regex("[\\\\/:*?\"<>|]"), "_") +} + /** * Cut cutOutRect from bitmap and return new bitmap, * return old bitmap if cutOutRect is null or malformed diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 7806bb73b..4f7dea946 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -62,7 +62,9 @@ +%randint% - a random number\n +%app% - name of the foreground app\n +%package% - package name of the foreground app]]> Tile action Tile long-press action Floating button action diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 426ca7e04..a87c58c57 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -62,7 +62,9 @@ +%randint% - случайное число\n +%app% - название приложения на переднем плане\n +%package% - имя пакета приложения на переднем плане]]> Действие плитки Действие при длительном нажатии на плитку Действие плавающей кнопки diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69558f70a..b5e90d853 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,7 +62,9 @@ +%randint% - a random number\n +%app% - name of the foreground app\n +%package% - package name of the foreground app]]> ‌Tile action ‌Tile long-press action ‌Floating button action From 5bcddd00d29262c556111c1fa0933e2f9b18751e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 21:47:34 +0000 Subject: [PATCH 03/10] Fix %app% and %package% always resolving to Screenshot Tile When taking a screenshot via Digital Assistant, Quick Settings tile, or floating button, the app's own window briefly comes to foreground. This causes onAccessibilityEvent to overwrite lastPackageName with the app's own package name before formatFileName() reads it. Filter out the app's own package in onAccessibilityEvent so that lastPackageName retains the actual foreground app. https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM --- .../screenshottile/services/ScreenshotAccessibilityService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index bc0577230..d7486320a 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -1043,7 +1043,8 @@ class ScreenshotAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && - event.packageName != lastPackageName + event.packageName != lastPackageName && + event.packageName != packageName ) { lastPackageName = event.packageName From 1bf20a283966b3d3f45f1d38f378c89d47d19464 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 21:47:34 +0000 Subject: [PATCH 04/10] Fix %app% and %package% always resolving to Screenshot Tile When taking a screenshot via Digital Assistant, Quick Settings tile, or floating button, the app's own window briefly comes to foreground. This causes onAccessibilityEvent to overwrite lastPackageName with the app's own package name before formatFileName() reads it. Filter out the app's own package in onAccessibilityEvent so that lastPackageName retains the actual foreground app. https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM --- .../screenshottile/services/ScreenshotAccessibilityService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index bc0577230..d7486320a 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -1043,7 +1043,8 @@ class ScreenshotAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && - event.packageName != lastPackageName + event.packageName != lastPackageName && + event.packageName != packageName ) { lastPackageName = event.packageName From 22201e33ec004c1a3f5c308c72cf43b39acb3fd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 22:30:52 +0000 Subject: [PATCH 05/10] Remove %app% from filename placeholders documentation %app% requires QUERY_ALL_PACKAGES to reliably resolve human-readable app labels via PackageManager. Without it, Android 11+ package visibility restrictions cause getApplicationInfo() to fail for most packages. The code still supports %app% for users who add it manually, but it is no longer advertised in the UI. Added a comment explaining the limitation. https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM --- .../main/java/com/github/cvzi/screenshottile/utils/Utils.kt | 3 +++ app/src/main/res/values-en/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index eedf785a8..0a986cc96 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -456,6 +456,9 @@ fun formatFileName(fileNamePattern: String, date: Date): String { val packageName = service?.getForegroundPackageName() ?: "" fileName = fileName.replace("%package%", sanitizeFilename(packageName)) if (fileName.contains("%app%")) { + // Resolving a human-readable app label requires QUERY_ALL_PACKAGES permission. + // Without it, getApplicationInfo() throws NameNotFoundException for most packages + // due to Android 11+ package visibility restrictions, falling back to raw package name. val appLabel = if (packageName.isNotEmpty()) { try { val context = App.getInstance() diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 4f7dea946..4209a0f21 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -63,7 +63,6 @@ %counter% - counts the number of screenshots\n %random% - a random UUID\n %randint% - a random number\n -%app% - name of the foreground app\n %package% - package name of the foreground app]]> Tile action Tile long-press action diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a87c58c57..143be02ee 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -63,7 +63,6 @@ %counter% - подсчет количества снимков экрана\n %random% - случайный UUID\n %randint% - случайное число\n -%app% - название приложения на переднем плане\n %package% - имя пакета приложения на переднем плане]]> Действие плитки Действие при длительном нажатии на плитку diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b5e90d853..7c8c1470c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,7 +63,6 @@ %counter% - counts the number of screenshots\n %random% - a random UUID\n %randint% - a random number\n -%app% - name of the foreground app\n %package% - package name of the foreground app]]> ‌Tile action ‌Tile long-press action From e9f29fa8f79b27cf90219f26f616808460f2627f Mon Sep 17 00:00:00 2001 From: cvzi Date: Fri, 13 Feb 2026 23:27:32 +0100 Subject: [PATCH 06/10] Revert "Remove %app% from filename placeholders documentation" This reverts commit 22201e33ec004c1a3f5c308c72cf43b39acb3fd4. --- .../main/java/com/github/cvzi/screenshottile/utils/Utils.kt | 3 --- app/src/main/res/values-en/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index 0a986cc96..eedf785a8 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -456,9 +456,6 @@ fun formatFileName(fileNamePattern: String, date: Date): String { val packageName = service?.getForegroundPackageName() ?: "" fileName = fileName.replace("%package%", sanitizeFilename(packageName)) if (fileName.contains("%app%")) { - // Resolving a human-readable app label requires QUERY_ALL_PACKAGES permission. - // Without it, getApplicationInfo() throws NameNotFoundException for most packages - // due to Android 11+ package visibility restrictions, falling back to raw package name. val appLabel = if (packageName.isNotEmpty()) { try { val context = App.getInstance() diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 4209a0f21..4f7dea946 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -63,6 +63,7 @@ %counter% - counts the number of screenshots\n %random% - a random UUID\n %randint% - a random number\n +%app% - name of the foreground app\n %package% - package name of the foreground app]]> Tile action Tile long-press action diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 143be02ee..a87c58c57 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -63,6 +63,7 @@ %counter% - подсчет количества снимков экрана\n %random% - случайный UUID\n %randint% - случайное число\n +%app% - название приложения на переднем плане\n %package% - имя пакета приложения на переднем плане]]> Действие плитки Действие при длительном нажатии на плитку diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c8c1470c..b5e90d853 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ %counter% - counts the number of screenshots\n %random% - a random UUID\n %randint% - a random number\n +%app% - name of the foreground app\n %package% - package name of the foreground app]]> ‌Tile action ‌Tile long-press action From 84c91af5eaf18824064600b1e5b2aef88d5226de Mon Sep 17 00:00:00 2001 From: cvzi Date: Fri, 13 Feb 2026 23:30:58 +0100 Subject: [PATCH 07/10] still run function even if event.packageName == packageName --- .../services/ScreenshotAccessibilityService.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index d7486320a..97104315f 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -1043,10 +1043,11 @@ class ScreenshotAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && - event.packageName != lastPackageName && - event.packageName != packageName + event.packageName != lastPackageName ) { - lastPackageName = event.packageName + if (event.packageName != packageName) { + lastPackageName = event.packageName + } if (!packageFilterEnabled) { return From 490d0f7657ed060cac5fc92bb870c13fa1ad2d37 Mon Sep 17 00:00:00 2001 From: cvzi Date: Fri, 13 Feb 2026 23:31:16 +0100 Subject: [PATCH 08/10] more robust label resolve for `%app%` --- .../github/cvzi/screenshottile/utils/Utils.kt | 90 ++++++++++++++----- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index eedf785a8..125ca8af1 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -451,36 +451,86 @@ fun formatFileName(fileNamePattern: String, date: Date): String { while (fileName.contains("%random%")) { fileName = fileName.replaceFirst("%random%", UUID.randomUUID().toString()) } - if (fileName.contains("%app%") || fileName.contains("%package%")) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && (fileName.contains("%app%") || fileName.contains("%package%"))) { val service = ScreenshotAccessibilityService.instance val packageName = service?.getForegroundPackageName() ?: "" fileName = fileName.replace("%package%", sanitizeFilename(packageName)) if (fileName.contains("%app%")) { - val appLabel = if (packageName.isNotEmpty()) { - try { - val context = App.getInstance() - val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.packageManager.getApplicationInfo( - packageName, - PackageManager.ApplicationInfoFlags.of(0) - ) - } else { - @Suppress("DEPRECATION") - context.packageManager.getApplicationInfo(packageName, 0) - } - context.packageManager.getApplicationLabel(appInfo).toString() - } catch (e: PackageManager.NameNotFoundException) { - packageName - } - } else { - "" - } + val appLabel = resolveAppLabel(packageName) fileName = fileName.replace("%app%", sanitizeFilename(appLabel)) } } return fileName } +private fun resolveAppLabel(packageName: String): String { + if (packageName.isBlank()) { + return "" + } + + val context = App.getInstance() + val pm = context.packageManager + + // Try launcher activity + try { + val launchIntent = pm.getLaunchIntentForPackage(packageName) + if (launchIntent != null) { + val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.resolveActivity( + launchIntent, + PackageManager.ResolveInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + pm.resolveActivity(launchIntent, 0) + } + + resolveInfo?.let { + val label = it.loadLabel(pm)?.toString() + if (!label.isNullOrBlank()) { + return label + } + } + } + } catch (_: Exception) { + // no-op + } + + // Try ApplicationInfo + try { + val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + pm.getApplicationInfo(packageName, 0) + } + + val label = pm.getApplicationLabel(appInfo)?.toString() + if (!label.isNullOrBlank()) { + return label + } + } catch (_: Exception) { + // no-op + } + + // Fallback + return shortPackageName(packageName) +} + +/** + * Get last part of package name + */ +private fun shortPackageName(pkg: String): String { + val parts = pkg.split('.') + return when { + parts.size >= 2 -> parts.takeLast(2).joinToString(".") + else -> pkg + } +} + /** * Remove characters that are not allowed in filenames */ From e86cc2711f225b4d01adacd1469ed9eb8a28de0b Mon Sep 17 00:00:00 2001 From: cvzi Date: Fri, 13 Feb 2026 23:36:00 +0100 Subject: [PATCH 09/10] Claude/screenshot app info qko v0 (#707) * Add %app% and %package% filename placeholders for foreground app info Allow users to include the foreground application name or package name in screenshot filenames. The accessibility service now tracks the foreground package unconditionally (not only when package filter is enabled), and formatFileName resolves the new placeholders via PackageManager.getApplicationLabel(). https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM * Fix %app% and %package% always resolving to Screenshot Tile When taking a screenshot via Digital Assistant, Quick Settings tile, or floating button, the app's own window briefly comes to foreground. This causes onAccessibilityEvent to overwrite lastPackageName with the app's own package name before formatFileName() reads it. Filter out the app's own package in onAccessibilityEvent so that lastPackageName retains the actual foreground app. https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM * Remove %app% from filename placeholders documentation %app% requires QUERY_ALL_PACKAGES to reliably resolve human-readable app labels via PackageManager. Without it, Android 11+ package visibility restrictions cause getApplicationInfo() to fail for most packages. The code still supports %app% for users who add it manually, but it is no longer advertised in the UI. Added a comment explaining the limitation. https://claude.ai/code/session_011TTQJFxqPwq3RD1tmtprvM * Revert "Remove %app% from filename placeholders documentation" This reverts commit 22201e33ec004c1a3f5c308c72cf43b39acb3fd4. * still run function even if event.packageName == packageName * more robust label resolve for `%app%` --------- Co-authored-by: Claude --- .../ScreenshotAccessibilityService.kt | 8 +- .../github/cvzi/screenshottile/utils/Utils.kt | 97 +++++++++++++++---- 2 files changed, 84 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index d7486320a..d59034990 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -1046,7 +1046,13 @@ class ScreenshotAccessibilityService : AccessibilityService() { event.packageName != lastPackageName && event.packageName != packageName ) { - lastPackageName = event.packageName + if (event.packageName != packageName) { + lastPackageName = event.packageName + } + + if (!packageFilterEnabled) { + return + } if (!packageFilterEnabled) { return diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index eedf785a8..1fde488a9 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -451,36 +451,93 @@ fun formatFileName(fileNamePattern: String, date: Date): String { while (fileName.contains("%random%")) { fileName = fileName.replaceFirst("%random%", UUID.randomUUID().toString()) } - if (fileName.contains("%app%") || fileName.contains("%package%")) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && (fileName.contains("%app%") || fileName.contains("%package%"))) { val service = ScreenshotAccessibilityService.instance val packageName = service?.getForegroundPackageName() ?: "" fileName = fileName.replace("%package%", sanitizeFilename(packageName)) if (fileName.contains("%app%")) { - val appLabel = if (packageName.isNotEmpty()) { - try { - val context = App.getInstance() - val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.packageManager.getApplicationInfo( - packageName, - PackageManager.ApplicationInfoFlags.of(0) - ) - } else { - @Suppress("DEPRECATION") - context.packageManager.getApplicationInfo(packageName, 0) - } - context.packageManager.getApplicationLabel(appInfo).toString() - } catch (e: PackageManager.NameNotFoundException) { - packageName - } - } else { - "" - } + val appLabel = resolveAppLabel(packageName) fileName = fileName.replace("%app%", sanitizeFilename(appLabel)) } } return fileName } +private fun resolveAppLabel(packageName: String): String { + if (packageName.isBlank()) { + return "" + } + + val context = App.getInstance() + val pm = context.packageManager + + // Try launcher activity + try { + val launchIntent = pm.getLaunchIntentForPackage(packageName) + if (launchIntent != null) { + val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.resolveActivity( + launchIntent, + PackageManager.ResolveInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + pm.resolveActivity(launchIntent, 0) + } + + resolveInfo?.let { + val label = it.loadLabel(pm)?.toString() + if (!label.isNullOrBlank()) { + return label + } + } + } + } catch (_: Exception) { + // no-op + } + + // Try ApplicationInfo + try { + val appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pm.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(0) + ) + } else { + @Suppress("DEPRECATION") + pm.getApplicationInfo(packageName, 0) + } + + val label = pm.getApplicationLabel(appInfo)?.toString() + if (!label.isNullOrBlank()) { + return label + } + } catch (_: Exception) { + // no-op + } + + // Fallback + return shortPackageName(packageName) +} + +/** + * Get last part of package name + */ +private fun shortPackageName(pkg: String): String { + val parts = pkg.split('.') + return when { + parts.size >= 2 -> parts.takeLast(2).joinToString(".") + else -> pkg + } +} + +/** + * Remove characters that are not allowed in filenames + */ +private fun sanitizeFilename(name: String): String { + return name.replace(Regex("[\\\\/:*?\"<>|]"), "_") +} + /** * Remove characters that are not allowed in filenames */ From ecdcb91489d2c9d1e27d955ea713495fe13e45f1 Mon Sep 17 00:00:00 2001 From: cvzi Date: Fri, 13 Feb 2026 23:54:44 +0100 Subject: [PATCH 10/10] Fix merge --- .../services/ScreenshotAccessibilityService.kt | 7 +------ .../java/com/github/cvzi/screenshottile/utils/Utils.kt | 7 ------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt index d59034990..97104315f 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/services/ScreenshotAccessibilityService.kt @@ -1043,8 +1043,7 @@ class ScreenshotAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { if (event.isFullScreen && event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && - event.packageName != lastPackageName && - event.packageName != packageName + event.packageName != lastPackageName ) { if (event.packageName != packageName) { lastPackageName = event.packageName @@ -1054,10 +1053,6 @@ class ScreenshotAccessibilityService : AccessibilityService() { return } - if (!packageFilterEnabled) { - return - } - if (packageFilterTempForceShow) { // Temporary show floating button despite filter until the next app change but at least 60s if (System.currentTimeMillis() - packageFilterTempOverrideTime < 60000) { diff --git a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt index 1fde488a9..125ca8af1 100644 --- a/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt +++ b/app/src/main/java/com/github/cvzi/screenshottile/utils/Utils.kt @@ -538,13 +538,6 @@ private fun sanitizeFilename(name: String): String { return name.replace(Regex("[\\\\/:*?\"<>|]"), "_") } -/** - * Remove characters that are not allowed in filenames - */ -private fun sanitizeFilename(name: String): String { - return name.replace(Regex("[\\\\/:*?\"<>|]"), "_") -} - /** * Cut cutOutRect from bitmap and return new bitmap, * return old bitmap if cutOutRect is null or malformed