Skip to content

Commit

Permalink
Merge pull request #2440 from square/py/no_notif
Browse files Browse the repository at this point in the history
Add support for disabling LeakCanary notifications
  • Loading branch information
pyricau committed Nov 10, 2022
2 parents 8141f03 + 9e3d3ee commit 7d3fe63
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 24 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -3,7 +3,7 @@ import org.jetbrains.dokka.gradle.DokkaTask
buildscript {
ext.versions = [
'minSdk' : 14,
'compileSdk': 31,
'compileSdk': 33,
]
repositories {
google()
Expand Down
11 changes: 7 additions & 4 deletions leakcanary-android-core/api/leakcanary-android-core.api
Expand Up @@ -94,14 +94,15 @@ public final class leakcanary/LeakCanary {

public final class leakcanary/LeakCanary$Config {
public fun <init> ()V
public fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)V
public synthetic fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZ)V
public synthetic fun <init> (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Z
public final fun component10 ()Z
public final fun component11 ()Lshark/LeakingObjectFinder;
public final fun component12 ()Lleakcanary/HeapDumper;
public final fun component13 ()Ljava/util/List;
public final fun component14 ()Z
public final fun component15 ()Z
public final fun component2 ()Z
public final fun component3 ()I
public final fun component4 ()Ljava/util/List;
Expand All @@ -110,8 +111,8 @@ public final class leakcanary/LeakCanary$Config {
public final fun component7 ()Lshark/MetadataExtractor;
public final fun component8 ()Z
public final fun component9 ()I
public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;Z)Lleakcanary/LeakCanary$Config;
public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZILjava/lang/Object;)Lleakcanary/LeakCanary$Config;
public final fun copy (ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZ)Lleakcanary/LeakCanary$Config;
public static synthetic fun copy$default (Lleakcanary/LeakCanary$Config;ZZILjava/util/List;Ljava/util/List;Lleakcanary/OnHeapAnalyzedListener;Lshark/MetadataExtractor;ZIZLshark/LeakingObjectFinder;Lleakcanary/HeapDumper;Ljava/util/List;ZZILjava/lang/Object;)Lleakcanary/LeakCanary$Config;
public fun equals (Ljava/lang/Object;)Z
public final fun getComputeRetainedHeapSize ()Z
public final fun getDumpHeap ()Z
Expand All @@ -126,6 +127,7 @@ public final class leakcanary/LeakCanary$Config {
public final fun getReferenceMatchers ()Ljava/util/List;
public final fun getRequestWriteExternalStoragePermission ()Z
public final fun getRetainedVisibleThreshold ()I
public final fun getShowNotifications ()Z
public final fun getUseExperimentalLeakFinders ()Z
public fun hashCode ()I
public final fun newBuilder ()Lleakcanary/LeakCanary$Config$Builder;
Expand All @@ -147,6 +149,7 @@ public final class leakcanary/LeakCanary$Config$Builder {
public final fun referenceMatchers (Ljava/util/List;)Lleakcanary/LeakCanary$Config$Builder;
public final fun requestWriteExternalStoragePermission (Z)Lleakcanary/LeakCanary$Config$Builder;
public final fun retainedVisibleThreshold (I)Lleakcanary/LeakCanary$Config$Builder;
public final fun showNotifications (Z)Lleakcanary/LeakCanary$Config$Builder;
public final fun useExperimentalLeakFinders (Z)Lleakcanary/LeakCanary$Config$Builder;
}

Expand Down
7 changes: 4 additions & 3 deletions leakcanary-android-core/src/main/AndroidManifest.xml
Expand Up @@ -22,8 +22,9 @@
<!-- To store the heap dumps and leak analysis results. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- To allow starting foreground services on Android P+ - https://developer.android.com/preview/behavior-changes#fg-svc -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<!-- To allow posting notifications on Android 13 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application>
<provider
Expand Down Expand Up @@ -92,7 +93,7 @@
</activity-alias>

<activity
android:name="leakcanary.internal.RequestStoragePermissionActivity"
android:name="leakcanary.internal.RequestPermissionActivity"
android:excludeFromRecents="true"
android:icon="@mipmap/leak_canary_icon"
android:label="@string/leak_canary_storage_permission_activity_label"
Expand Down
18 changes: 17 additions & 1 deletion leakcanary-android-core/src/main/java/leakcanary/LeakCanary.kt
Expand Up @@ -208,6 +208,15 @@ object LeakCanary {
}
),

/**
* Whether to show LeakCanary notifications. When [showNotifications] is true, LeakCanary
* will only display notifications if the app is in foreground and is not an instant, TV or
* Wear app.
*
* Defaults to true.
*/
val showNotifications: Boolean = true,

/**
* Deprecated: This is a no-op, set a custom [leakingObjectFinder] instead.
*/
Expand Down Expand Up @@ -256,6 +265,7 @@ object LeakCanary {
private var heapDumper = config.heapDumper
private var eventListeners = config.eventListeners
private var useExperimentalLeakFinders = config.useExperimentalLeakFinders
private var showNotifications = config.showNotifications

/** @see [LeakCanary.Config.dumpHeap] */
fun dumpHeap(dumpHeap: Boolean) =
Expand Down Expand Up @@ -315,6 +325,11 @@ object LeakCanary {
fun useExperimentalLeakFinders(useExperimentalLeakFinders: Boolean) =
apply { this.useExperimentalLeakFinders = useExperimentalLeakFinders }

/** @see [LeakCanary.Config.showNotifications] */
fun showNotifications(showNotifications: Boolean) =
apply { this.showNotifications = showNotifications }


fun build() = config.copy(
dumpHeap = dumpHeap,
dumpHeapWhenDebugging = dumpHeapWhenDebugging,
Expand All @@ -329,7 +344,8 @@ object LeakCanary {
leakingObjectFinder = leakingObjectFinder,
heapDumper = heapDumper,
eventListeners = eventListeners,
useExperimentalLeakFinders = useExperimentalLeakFinders
useExperimentalLeakFinders = useExperimentalLeakFinders,
showNotifications = showNotifications,
)
}
}
Expand Down
Expand Up @@ -93,12 +93,13 @@ internal class LeakDirectoryProvider constructor(
}

fun requestWritePermissionNotification() {
if (permissionNotificationDisplayed) {
if (permissionNotificationDisplayed || !Notifications.canShowNotification) {
return
}
permissionNotificationDisplayed = true

val pendingIntent = RequestStoragePermissionActivity.createPendingIntent(context)
val pendingIntent =
RequestPermissionActivity.createPendingIntent(context, WRITE_EXTERNAL_STORAGE)
val contentTitle = context.getString(
R.string.leak_canary_permission_notification_title
)
Expand Down
Expand Up @@ -15,6 +15,7 @@
*/
package leakcanary.internal

import android.Manifest.permission.POST_NOTIFICATIONS
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
Expand All @@ -24,17 +25,57 @@ import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.O
import com.squareup.leakcanary.core.R
import leakcanary.LeakCanary
import leakcanary.internal.InternalLeakCanary.FormFactor.MOBILE
import shark.SharkLog

internal object Notifications {

private var notificationPermissionRequested = false

// Instant apps cannot show background notifications
// See https://github.com/square/leakcanary/issues/1197
// TV devices can't show notifications.
// Watch devices: not sure, but probably not a good idea anyway?
val canShowNotification: Boolean
get() = InternalLeakCanary.formFactor == MOBILE &&
(!InternalLeakCanary.isInstantApp || InternalLeakCanary.applicationVisible)
get() {
if (InternalLeakCanary.formFactor != MOBILE) {
return false
}
if (InternalLeakCanary.isInstantApp || !InternalLeakCanary.applicationVisible) {
return false
}
if (!LeakCanary.config.showNotifications) {
return false
}
if (SDK_INT >= 33) {
val application = InternalLeakCanary.application
if (application.applicationInfo.targetSdkVersion >= 33) {
val notificationManager =
application.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (!notificationManager.areNotificationsEnabled()) {
if (notificationPermissionRequested) {
SharkLog.d { "Not showing notification: already requested missing POST_NOTIFICATIONS permission." }
} else {
SharkLog.d { "Not showing notification: requesting missing POST_NOTIFICATIONS permission." }
application.startActivity(
RequestPermissionActivity.createIntent(
application,
POST_NOTIFICATIONS
)
)
notificationPermissionRequested = true
}
return false
}
if (notificationManager.areNotificationsPaused()) {
SharkLog.d { "Not showing notification, notifications are paused." }
return false
}
}
}
return true
}

@Suppress("LongParameterList")
fun showNotification(
Expand Down
Expand Up @@ -15,7 +15,6 @@
*/
package leakcanary.internal

import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.annotation.TargetApi
import android.app.Activity
import android.app.PendingIntent
Expand All @@ -33,17 +32,20 @@ import android.widget.Toast.LENGTH_LONG
import com.squareup.leakcanary.core.R

@TargetApi(Build.VERSION_CODES.M) //
internal class RequestStoragePermissionActivity : Activity() {
internal class RequestPermissionActivity : Activity() {

private val targetPermission: String
get() = intent.getStringExtra(TARGET_PERMISSION_EXTRA)!!

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (savedInstanceState == null) {
if (hasStoragePermission()) {
if (hasTargetPermission()) {
finish()
return
}
val permissions = arrayOf(WRITE_EXTERNAL_STORAGE)
val permissions = arrayOf(targetPermission)
requestPermissions(permissions, 42)
}
}
Expand All @@ -53,7 +55,7 @@ internal class RequestStoragePermissionActivity : Activity() {
permissions: Array<String>,
grantResults: IntArray
) {
if (!hasStoragePermission()) {
if (!hasTargetPermission()) {
Toast.makeText(application, R.string.leak_canary_permission_not_granted, LENGTH_LONG)
.show()
}
Expand All @@ -66,15 +68,22 @@ internal class RequestStoragePermissionActivity : Activity() {
super.finish()
}

private fun hasStoragePermission(): Boolean {
return checkSelfPermission(WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED
private fun hasTargetPermission(): Boolean {
return checkSelfPermission(targetPermission) == PERMISSION_GRANTED
}

companion object {
private const val TARGET_PERMISSION_EXTRA = "targetPermission"

fun createIntent(context: Context, permission: String): Intent {
return Intent(context, RequestPermissionActivity::class.java).apply {
flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP
putExtra(TARGET_PERMISSION_EXTRA, permission)
}
}

fun createPendingIntent(context: Context): PendingIntent {
val intent = Intent(context, RequestStoragePermissionActivity::class.java)
intent.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TOP
fun createPendingIntent(context: Context, permission: String): PendingIntent {
val intent = createIntent(context, permission)
val flags = if (Build.VERSION.SDK_INT >= 23) {
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE
} else {
Expand Down
Expand Up @@ -63,7 +63,7 @@
<string name="leak_canary_delete">Delete</string>
<string name="leak_canary_delete_all">Delete all</string>
<string name="leak_canary_delete_all_leaks_title">Are you sure you want to delete all leaks?</string>
<string name="leak_canary_permission_not_granted">Please grant external storage permission, otherwise memory leaks will not be detected.</string>
<string name="leak_canary_permission_not_granted">Please grant requested permission, otherwise memory leak detection may not work.</string>
<string name="leak_canary_permission_notification_title">Leak detected, need permission</string>
<string name="leak_canary_permission_notification_text">Click to enable storage permission for %s.</string>
<string name="leak_canary_shortcut_label">Leaks</string>
Expand Down

0 comments on commit 7d3fe63

Please sign in to comment.