From 8efb01b46c6ca41eda8585b15612b64a7f9f5e04 Mon Sep 17 00:00:00 2001 From: Sahil Jain Date: Tue, 28 Jun 2022 18:26:49 -0700 Subject: [PATCH] [WIP] Move Notifications to get triggered from NotificationEventListener --- .../src/main/java/leakcanary/EventListener.kt | 8 ++ .../leakcanary/NotificationEventListener.kt | 92 ++++++++++++++++++- .../leakcanary/internal/HeapDumpTrigger.kt | 46 +--------- .../leakcanary/internal/InternalLeakCanary.kt | 5 +- 4 files changed, 106 insertions(+), 45 deletions(-) diff --git a/leakcanary-android-core/src/main/java/leakcanary/EventListener.kt b/leakcanary-android-core/src/main/java/leakcanary/EventListener.kt index d814a1edac..cdb8a46537 100644 --- a/leakcanary-android-core/src/main/java/leakcanary/EventListener.kt +++ b/leakcanary-android-core/src/main/java/leakcanary/EventListener.kt @@ -86,6 +86,14 @@ fun interface EventListener { showIntent: Intent ) : HeapAnalysisDone(uniqueId, heapAnalysis, showIntent) } + + class NoRetainedObjectFound( + uniqueId: String, + ) : Event(uniqueId) + + class HeapDumpReceived( + uniqueId: String, + ) : Event(uniqueId) } /** diff --git a/leakcanary-android-core/src/main/java/leakcanary/NotificationEventListener.kt b/leakcanary-android-core/src/main/java/leakcanary/NotificationEventListener.kt index c13213d07a..7c6b631ef1 100644 --- a/leakcanary-android-core/src/main/java/leakcanary/NotificationEventListener.kt +++ b/leakcanary-android-core/src/main/java/leakcanary/NotificationEventListener.kt @@ -5,24 +5,44 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.os.Build +import android.os.Handler +import android.os.HandlerThread import com.squareup.leakcanary.core.R import leakcanary.EventListener.Event import leakcanary.EventListener.Event.DumpingHeap import leakcanary.EventListener.Event.HeapAnalysisDone import leakcanary.EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded import leakcanary.EventListener.Event.HeapAnalysisProgress -import leakcanary.EventListener.Event.HeapDumpFailed import leakcanary.EventListener.Event.HeapDump +import leakcanary.EventListener.Event.HeapDumpFailed import leakcanary.internal.InternalLeakCanary +import leakcanary.internal.NotificationReceiver import leakcanary.internal.NotificationType.LEAKCANARY_LOW import leakcanary.internal.NotificationType.LEAKCANARY_MAX import leakcanary.internal.Notifications +import leakcanary.internal.RetainInstanceEvent + +private const val BACKGROUND_THREAD_NAME = "Leakcanary-Notification-Event-listener" +private const val DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS = 30_000L object NotificationEventListener : EventListener { private val appContext = InternalLeakCanary.application private val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val backgroundHandler: Handler + private val scheduleDismissNoRetainedOnTapNotification = { + dismissNoRetainedOnTapNotification() + } + private val scheduleDismissRetainedCountNotification = { + dismissRetainedCountNotification() + } + + init { + val handlerThread = HandlerThread(BACKGROUND_THREAD_NAME) + handlerThread.start() + backgroundHandler = Handler(handlerThread.looper) + } override fun onEvent(event: Event) { // TODO Unify Notifications.buildNotification vs Notifications.showNotification @@ -32,6 +52,7 @@ object NotificationEventListener : EventListener { } when (event) { is DumpingHeap -> { + dismissRetainedCountNotification() val dumpingHeap = appContext.getString(R.string.leak_canary_notification_dumping) val builder = Notification.Builder(appContext) .setContentTitle(dumpingHeap) @@ -72,12 +93,77 @@ object NotificationEventListener : EventListener { } else { PendingIntent.FLAG_UPDATE_CURRENT } - val pendingIntent = PendingIntent.getActivity(appContext, 1, event.showIntent, flags) - showHeapAnalysisResultNotification(contentTitle,pendingIntent) + val pendingIntent = PendingIntent.getActivity(appContext, 1, event.showIntent, flags) + showHeapAnalysisResultNotification(contentTitle, pendingIntent) + } + + is Event.HeapDumpReceived -> { + dismissNoRetainedOnTapNotification() + } + + is Event.NoRetainedObjectFound -> { + val builder = Notification.Builder(appContext) + .setContentTitle( + appContext.getString(R.string.leak_canary_notification_no_retained_object_title) + ) + .setContentText( + appContext.getString( + R.string.leak_canary_notification_no_retained_object_content + ) + ) + .setAutoCancel(true) + .setContentIntent( + NotificationReceiver.pendingIntent( + appContext, + NotificationReceiver.Action.CANCEL_NOTIFICATION + ) + ) + val notification = + Notifications.buildNotification(appContext, builder, LEAKCANARY_LOW) + notificationManager.notify( + R.id.leak_canary_notification_no_retained_object_on_tap, notification + ) + + backgroundHandler.postDelayed( + scheduleDismissNoRetainedOnTapNotification, + DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS + ) } } } + private fun dismissNoRetainedOnTapNotification() { + backgroundHandler.removeCallbacks(scheduleDismissNoRetainedOnTapNotification) + notificationManager.cancel(R.id.leak_canary_notification_no_retained_object_on_tap) + } + + private fun dismissRetainedCountNotification() { + backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification) + notificationManager.cancel(R.id.leak_canary_notification_retained_objects) + } + + private fun showRetainedCountNotification( + objectCount: Int, + contentText: String + ) { + backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification) + if (!Notifications.canShowNotification) { + return + } + @Suppress("DEPRECATION") + val builder = Notification.Builder(appContext) + .setContentTitle( + appContext.getString(R.string.leak_canary_notification_retained_title, objectCount) + ) + .setContentText(contentText) + .setAutoCancel(true) + .setContentIntent(NotificationReceiver.pendingIntent(appContext, NotificationReceiver.Action.DUMP_HEAP)) + val notification = + Notifications.buildNotification(appContext, builder, LEAKCANARY_LOW) + notificationManager.notify(R.id.leak_canary_notification_retained_objects, notification) + } + + private fun showHeapAnalysisResultNotification(contentTitle: String, showIntent: PendingIntent) { val contentText = appContext.getString(R.string.leak_canary_notification_message) Notifications.showNotification( diff --git a/leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt b/leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt index fdd98c17d9..11940a9b1b 100644 --- a/leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt +++ b/leakcanary-android-core/src/main/java/leakcanary/internal/HeapDumpTrigger.kt @@ -10,6 +10,8 @@ import android.os.SystemClock import com.squareup.leakcanary.core.R import java.util.UUID import leakcanary.AppWatcher +import leakcanary.EventListener.Event.HeapDumpReceived +import leakcanary.EventListener.Event.NoRetainedObjectFound import leakcanary.EventListener.Event.DumpingHeap import leakcanary.EventListener.Event.HeapDump import leakcanary.EventListener.Event.HeapDumpFailed @@ -53,14 +55,6 @@ internal class HeapDumpTrigger( private var lastHeapDumpUptimeMillis = 0L - private val scheduleDismissRetainedCountNotification = { - dismissRetainedCountNotification() - } - - private val scheduleDismissNoRetainedOnTapNotification = { - dismissNoRetainedOnTapNotification() - } - /** * When the app becomes invisible, we don't dump the heap immediately. Instead we wait in case * the app came back to the foreground, but also to wait for new leaks that typically occur on @@ -154,7 +148,6 @@ internal class HeapDumpTrigger( return } - dismissRetainedCountNotification() val visibility = if (applicationVisible) "visible" else "not visible" dumpHeap( retainedReferenceCount = retainedReferenceCount, @@ -237,32 +230,14 @@ internal class HeapDumpTrigger( fun onDumpHeapReceived(forceDump: Boolean) { backgroundHandler.post { - dismissNoRetainedOnTapNotification() + InternalLeakCanary.sendEvent(HeapDumpReceived(currentEventUniqueId!!)) gcTrigger.runGc() val retainedReferenceCount = objectWatcher.retainedObjectCount if (!forceDump && retainedReferenceCount == 0) { SharkLog.d { "Ignoring user request to dump heap: no retained objects remaining after GC" } @Suppress("DEPRECATION") - val builder = Notification.Builder(application) - .setContentTitle( - application.getString(R.string.leak_canary_notification_no_retained_object_title) - ) - .setContentText( - application.getString( - R.string.leak_canary_notification_no_retained_object_content - ) - ) - .setAutoCancel(true) - .setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION)) - val notification = - Notifications.buildNotification(application, builder, LEAKCANARY_LOW) - notificationManager.notify( - R.id.leak_canary_notification_no_retained_object_on_tap, notification - ) - backgroundHandler.postDelayed( - scheduleDismissNoRetainedOnTapNotification, - DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS - ) + InternalLeakCanary.sendEvent(NoRetainedObjectFound(currentEventUniqueId!!)) + lastDisplayedRetainedObjectCount = 0 return@post } @@ -403,20 +378,9 @@ internal class HeapDumpTrigger( notificationManager.notify(R.id.leak_canary_notification_retained_objects, notification) } - private fun dismissRetainedCountNotification() { - backgroundHandler.removeCallbacks(scheduleDismissRetainedCountNotification) - notificationManager.cancel(R.id.leak_canary_notification_retained_objects) - } - - private fun dismissNoRetainedOnTapNotification() { - backgroundHandler.removeCallbacks(scheduleDismissNoRetainedOnTapNotification) - notificationManager.cancel(R.id.leak_canary_notification_no_retained_object_on_tap) - } - companion object { internal const val WAIT_AFTER_DUMP_FAILED_MILLIS = 5_000L private const val WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L - private const val DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS = 30_000L private const val WAIT_BETWEEN_HEAP_DUMPS_MILLIS = 60_000L } } diff --git a/leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt b/leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt index 877a973fbe..f048f0c131 100644 --- a/leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt +++ b/leakcanary-android-core/src/main/java/leakcanary/internal/InternalLeakCanary.kt @@ -137,7 +137,10 @@ internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedList val backgroundHandler = Handler(handlerThread.looper) heapDumpTrigger = HeapDumpTrigger( - application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, + application, + backgroundHandler, + AppWatcher.objectWatcher, + gcTrigger, configProvider ) application.registerVisibilityListener { applicationVisible ->