Skip to content

Commit

Permalink
Merge pull request #2170 from DataDog/mconstantin/rum-5750/handle-sec…
Browse files Browse the repository at this point in the history
…urity-issue-on-broadcast-receivers

RUM-5750 Use NO_EXPORT_FLAG for BroadcastReceiver on API above 26
  • Loading branch information
mariusc83 authored Aug 12, 2024
2 parents fc49edd + 6232b7d commit 2f2ab8b
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ internal abstract class ThreadSafeReceiver : BroadcastReceiver() {

val isRegistered = AtomicBoolean(false)

@SuppressLint("UnspecifiedRegisterReceiverFlag")
// We suppress the warning here as this method is not available on all Android versions
@SuppressLint("WrongConstant", "UnspecifiedRegisterReceiverFlag")
fun registerReceiver(
context: Context,
filter: IntentFilter
): Intent? {
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.registerReceiver(this, filter, RECEIVER_NOT_EXPORTED_COMPAT)
} else {
context.registerReceiver(this, filter)
}
Expand All @@ -37,4 +40,8 @@ internal abstract class ThreadSafeReceiver : BroadcastReceiver() {
context.unregisterReceiver(this)
}
}

companion object {
internal const val RECEIVER_NOT_EXPORTED_COMPAT: Int = 0x4
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.core.internal.receiver

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.datadog.android.utils.forge.Configurator
import com.datadog.tools.unit.annotations.TestTargetApi
import com.datadog.tools.unit.extensions.ApiLevelExtension
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
import fr.xgouchet.elmyr.junit5.ForgeExtension
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.extension.Extensions
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.junit.jupiter.MockitoSettings
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.quality.Strictness

@Extensions(
ExtendWith(MockitoExtension::class),
ExtendWith(ForgeExtension::class),
ExtendWith(ApiLevelExtension::class)
)
@MockitoSettings(strictness = Strictness.LENIENT)
@ForgeConfiguration(Configurator::class)
internal class ThreadSafeReceiverTest {
private lateinit var testedReceiver: ThreadSafeReceiver

@Mock
lateinit var mockContext: Context

@Mock
lateinit var mockIntentFilter: IntentFilter

@BeforeEach
fun `set up`() {
testedReceiver = TestableThreadSafeReceiver()
}

// region registerReceiver

@TestTargetApi(Build.VERSION_CODES.O)
@Test
fun `M use the no export flag W registerReceiver { API version above 26}`() {
// When
testedReceiver.registerReceiver(mockContext, mockIntentFilter)

// Then
verify(mockContext).registerReceiver(
testedReceiver,
mockIntentFilter,
ThreadSafeReceiver.RECEIVER_NOT_EXPORTED_COMPAT
)
assertThat(this.testedReceiver.isRegistered.get()).isTrue()
}

@TestTargetApi(Build.VERSION_CODES.TIRAMISU)
@Test
fun `M use the no export flag W registerReceiver { API version above 33}`() {
// When
testedReceiver.registerReceiver(mockContext, mockIntentFilter)

// Then
verify(mockContext).registerReceiver(
testedReceiver,
mockIntentFilter,
Context.RECEIVER_NOT_EXPORTED
)
assertThat(this.testedReceiver.isRegistered.get()).isTrue()
}

@SuppressLint("UnspecifiedRegisterReceiverFlag")
@TestTargetApi(Build.VERSION_CODES.N)
@Test
fun `M not use the no export flag W registerReceiver { API version below 26}`() {
// When
testedReceiver.registerReceiver(mockContext, mockIntentFilter)

// Then
verify(mockContext).registerReceiver(testedReceiver, mockIntentFilter)
verifyNoMoreInteractions(mockContext)
assertThat(this.testedReceiver.isRegistered.get()).isTrue()
}

// endregion

// region unregisterReceiver

@Test
fun `M unregister the receiver W unregisterReceiver { registered }`() {
// Given
testedReceiver.isRegistered.set(true)

// When
testedReceiver.unregisterReceiver(mockContext)

// Then
verify(mockContext).unregisterReceiver(testedReceiver)
assertThat(this.testedReceiver.isRegistered.get()).isFalse()
}

@Test
fun `M do nothing W unregisterReceiver { not registered }`() {
// Given
testedReceiver.isRegistered.set(false)

// When
testedReceiver.unregisterReceiver(mockContext)

// Then
verifyNoInteractions(mockContext)
assertThat(this.testedReceiver.isRegistered.get()).isFalse()
}

// endregion
}

internal class TestableThreadSafeReceiver : ThreadSafeReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {}
}

0 comments on commit 2f2ab8b

Please sign in to comment.