From 9f880c5f1c4eb3d2d370d50820f638243cdfef63 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Mon, 27 Jul 2026 20:54:22 +0000 Subject: [PATCH] Request listener rebind on startup --- .../se/ajpanton/notificationlog/ViewLogsFragment.kt | 12 ++++++++++++ .../NotificationListenerComponentController.kt | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt index 9c40cb1..dfea157 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt @@ -2,6 +2,8 @@ package se.ajpanton.notificationlog import android.graphics.BitmapFactory import android.os.Bundle +import android.content.ComponentName +import android.app.NotificationManager import android.text.TextUtils import android.view.View import android.widget.ImageView @@ -18,6 +20,7 @@ import se.ajpanton.notificationlog.settings.LogViewSettings import se.ajpanton.notificationlog.settings.LogViewSettingsStore import se.ajpanton.notificationlog.settings.TimestampZone import se.ajpanton.notificationlog.settings.DisplayEvent +import se.ajpanton.notificationlog.capture.NotificationCaptureService import java.text.DateFormat import java.util.Date import androidx.fragment.app.Fragment @@ -91,6 +94,11 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { private fun render(view: FragmentViewLogsBinding, addedRows: List, reset: Boolean) { if (reset) view.logRows.removeAllViews() view.emptyView.visibility = if (rows.isEmpty()) View.VISIBLE else View.GONE + view.emptyView.text = if (hasNotificationAccess()) { + "No logs yet." + } else { + "Notification access is disabled. Enable it in Settings." + } val settings = LogViewSettingsStore(requireContext()).load() addedRows.filter { eventFor(it.entry.action) in settings.visibleEvents } .forEach { row -> view.logRows.addView(logRowView(row, settings)) } @@ -264,6 +272,10 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) { else -> DisplayEvent.DISAPPEARING } + private fun hasNotificationAccess(): Boolean = requireContext() + .getSystemService(NotificationManager::class.java) + .isNotificationListenerAccessGranted(ComponentName(requireContext(), NotificationCaptureService::class.java)) + private fun timestamp(value: Long, zone: TimestampZone, eventTimeZoneId: String?): String = DateFormat.getDateTimeInstance().apply { timeZone = when (zone) { TimestampZone.UTC -> java.util.TimeZone.getTimeZone("UTC") diff --git a/app/src/main/java/se/ajpanton/notificationlog/settings/NotificationListenerComponentController.kt b/app/src/main/java/se/ajpanton/notificationlog/settings/NotificationListenerComponentController.kt index a374a8f..2498a06 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/settings/NotificationListenerComponentController.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/settings/NotificationListenerComponentController.kt @@ -22,10 +22,14 @@ internal object NotificationListenerComponentController { } else { PackageManager.COMPONENT_ENABLED_STATE_DISABLED } - if (packageManager.getComponentEnabledSetting(component) == desired) return - packageManager.setComponentEnabledSetting(component, desired, PackageManager.DONT_KILL_APP) + if (packageManager.getComponentEnabledSetting(component) != desired) { + packageManager.setComponentEnabledSetting(component, desired, PackageManager.DONT_KILL_APP) + } if (desired == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { try { + // Data clearing and OEM listener management can leave an enabled + // component without a live binding. Ask Android to reconnect on + // every enabled-rule synchronization, not just a state change. NotificationListenerService.requestRebind(component) } catch (error: SecurityException) { Log.w(TAG, "Notification access is not granted yet; Android will bind after the user grants it", error)