Skip to content

Commit

Permalink
[WIP] Move Notifications to get triggered from NotificationEventListener
Browse files Browse the repository at this point in the history
  • Loading branch information
sjain-sc committed Jun 29, 2022
1 parent 013c7fd commit 6388c9c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 45 deletions.
Expand Up @@ -86,6 +86,14 @@ fun interface EventListener {
showIntent: Intent
) : HeapAnalysisDone<HeapAnalysisFailure>(uniqueId, heapAnalysis, showIntent)
}

class NoRetainedObjectFound(
uniqueId: String,
) : Event(uniqueId)

class HeapDumpReceived(
uniqueId: String,
) : Event(uniqueId)
}

/**
Expand Down
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -154,7 +148,6 @@ internal class HeapDumpTrigger(
return
}

dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
}
Expand Up @@ -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 ->
Expand Down

0 comments on commit 6388c9c

Please sign in to comment.