diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvent.kt index 1eff07db7fa..a93d48edaea 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvent.kt @@ -60,14 +60,14 @@ internal data class SessionInfo( /** What order this Session came in this run of the app. For the first Session this will be 0. */ val sessionIndex: Int, - /** Tracks when the event was initiated */ - var eventTimestampUs: Long, + /** Tracks when the event was initiated. */ + val eventTimestampUs: Long, /** Data collection status of the dependent product SDKs. */ - var dataCollectionStatus: DataCollectionStatus = DataCollectionStatus(), + val dataCollectionStatus: DataCollectionStatus = DataCollectionStatus(), /** Identifies a unique device+app installation: go/firebase-installations */ - var firebaseInstallationId: String = "", + val firebaseInstallationId: String = "", ) /** Contains the data collection state for all dependent SDKs and sampling info */ diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt index 64588a52c79..f53b2957a18 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionEvents.kt @@ -32,16 +32,13 @@ internal object SessionEvents { .ignoreNullValues(true) .build() - /** - * Construct a Session Start event. - * - * Some mutable fields, e.g. firebaseInstallationId, get populated later. - */ + /** Construct a Session Start event. */ fun buildSession( firebaseApp: FirebaseApp, sessionDetails: SessionDetails, sessionsSettings: SessionsSettings, subscribers: Map = emptyMap(), + firebaseInstallationId: String = "", ) = SessionEvent( eventType = EventType.SESSION_START, @@ -56,6 +53,7 @@ internal object SessionEvents { crashlytics = toDataCollectionState(subscribers[SessionSubscriber.Name.CRASHLYTICS]), sessionSamplingRate = sessionsSettings.samplingRate, ), + firebaseInstallationId, ), applicationInfo = getApplicationInfo(firebaseApp) ) diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionFirelogPublisher.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionFirelogPublisher.kt index 91cf9062fe0..131cb20990d 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionFirelogPublisher.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionFirelogPublisher.kt @@ -47,22 +47,23 @@ internal class SessionFirelogPublisher( * This will pull all the necessary information about the device in order to create a full * [SessionEvent], and then upload that through the Firelog interface. */ - fun logSession(sessionDetails: SessionDetails) { + fun logSession(sessionDetails: SessionDetails) = CoroutineScope(backgroundDispatcher).launch { - val sessionEvent = - SessionEvents.buildSession( - firebaseApp, - sessionDetails, - sessionSettings, - FirebaseSessionsDependencies.getRegisteredSubscribers(), + if (shouldLogSession()) { + attemptLoggingSessionEvent( + SessionEvents.buildSession( + firebaseApp, + sessionDetails, + sessionSettings, + FirebaseSessionsDependencies.getRegisteredSubscribers(), + getFirebaseInstallationId(), + ) ) - sessionEvent.sessionData.firebaseInstallationId = getFid() - attemptLoggingSessionEvent(sessionEvent) + } } - } /** Attempts to write the given [SessionEvent] to firelog. Failures are logged and ignored. */ - private suspend fun attemptLoggingSessionEvent(sessionEvent: SessionEvent) { + private fun attemptLoggingSessionEvent(sessionEvent: SessionEvent) { try { eventGDTLogger.log(sessionEvent) Log.d(TAG, "Successfully logged Session Start event: ${sessionEvent.sessionData.sessionId}") @@ -71,8 +72,28 @@ internal class SessionFirelogPublisher( } } + /** Determines if the SDK should log a session to Firelog. */ + private suspend fun shouldLogSession(): Boolean { + Log.d(TAG, "Data Collection is enabled for at least one Subscriber") + + // This will cause remote settings to be fetched if the cache is expired. + sessionSettings.updateSettings() + + if (!sessionSettings.sessionsEnabled) { + Log.d(TAG, "Sessions SDK disabled. Events will not be sent.") + return false + } + + if (!shouldCollectEvents()) { + Log.d(TAG, "Sessions SDK has dropped this session due to sampling.") + return false + } + + return true + } + /** Gets the Firebase Installation ID for the current app installation. */ - private suspend fun getFid() = + private suspend fun getFirebaseInstallationId() = try { firebaseInstallations.id.await() } catch (ex: Exception) { @@ -81,8 +102,16 @@ internal class SessionFirelogPublisher( "" } + /** Calculate whether we should sample events using [SessionsSettings] data. */ + private fun shouldCollectEvents(): Boolean { + // Sampling rate of 1 means the SDK will send every event. + return randomValueForSampling <= sessionSettings.samplingRate + } + internal companion object { - const val TAG = "SessionFirelogPublisher" + private const val TAG = "SessionFirelogPublisher" + + private val randomValueForSampling: Double = Math.random() val instance: SessionFirelogPublisher get() = Firebase.app.get(SessionFirelogPublisher::class.java) diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleClient.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleClient.kt index bbdcd3e55d2..ac818eb105c 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleClient.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleClient.kt @@ -152,7 +152,9 @@ internal object SessionLifecycleClient { private fun sendLifecycleEvent(messageCode: Int) { val allMessages = drainQueue() allMessages.add(Message.obtain(null, messageCode, 0, 0)) - sendLifecycleEvents(allMessages) + CoroutineScope(Dispatchers.instance.backgroundDispatcher).launch { + sendLifecycleEvents(allMessages) + } } /** @@ -160,15 +162,26 @@ internal object SessionLifecycleClient { * FOREGROUND and BACKGROUND events to the service that are included in the given list. Running * through the full backlog of messages is not useful since the service only cares about the * current state and transitions from background -> foreground. + * + * Does not send events unless data collection is enabled for at least one subscriber. */ - private fun sendLifecycleEvents(messages: List) { - val latest = ArrayList() - getLatestByCode(messages, SessionLifecycleService.BACKGROUNDED)?.let { latest.add(it) } - getLatestByCode(messages, SessionLifecycleService.FOREGROUNDED)?.let { latest.add(it) } - latest.sortBy { it.getWhen() } - - latest.forEach { sendMessageToServer(it) } - } + private fun sendLifecycleEvents(messages: List) = + CoroutineScope(Dispatchers.instance.backgroundDispatcher).launch { + val subscribers = FirebaseSessionsDependencies.getRegisteredSubscribers() + if (subscribers.isEmpty()) { + Log.d( + TAG, + "Sessions SDK did not have any dependent SDKs register as dependencies. Events will not be sent." + ) + } else if (subscribers.values.none { it.isDataCollectionEnabled }) { + Log.d(TAG, "Data Collection is disabled for all subscribers. Skipping this Event") + } else { + val latest = ArrayList(2) + getLatestByCode(messages, SessionLifecycleService.BACKGROUNDED)?.let { latest.add(it) } + getLatestByCode(messages, SessionLifecycleService.FOREGROUNDED)?.let { latest.add(it) } + latest.sortedBy { it.getWhen() }.forEach { sendMessageToServer(it) } + } + } /** Sends the given [Message] to the [SessionLifecycleService]. */ private fun sendMessageToServer(msg: Message) { diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt index 529a70ebc08..c334293e692 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionLifecycleService.kt @@ -40,10 +40,10 @@ import kotlinx.coroutines.launch * be generated. When this happens, the service will broadcast the updated session id to all * connected clients. */ -internal class SessionLifecycleService() : Service() { +internal class SessionLifecycleService : Service() { /** The thread that will be used to process all lifecycle messages from connected clients. */ - private var handlerThread: HandlerThread = HandlerThread("FirebaseSessions_HandlerThread") + private val handlerThread: HandlerThread = HandlerThread("FirebaseSessions_HandlerThread") /** The handler that will process all lifecycle messages from connected clients . */ private var messageHandler: MessageHandler? = null diff --git a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionFirelogPublisherTest.kt b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionFirelogPublisherTest.kt index 918d146d762..67a80fde897 100644 --- a/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionFirelogPublisherTest.kt +++ b/firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionFirelogPublisherTest.kt @@ -20,10 +20,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import com.google.firebase.FirebaseApp import com.google.firebase.concurrent.TestOnlyExecutors +import com.google.firebase.sessions.api.FirebaseSessionsDependencies +import com.google.firebase.sessions.api.SessionSubscriber import com.google.firebase.sessions.settings.SessionsSettings import com.google.firebase.sessions.testing.FakeEventGDTLogger import com.google.firebase.sessions.testing.FakeFirebaseApp import com.google.firebase.sessions.testing.FakeFirebaseInstallations +import com.google.firebase.sessions.testing.FakeSessionSubscriber import com.google.firebase.sessions.testing.FakeSettingsProvider import com.google.firebase.sessions.testing.TestSessionEventData import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -31,12 +34,27 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class SessionFirelogPublisherTest { + @Before + fun setUp() { + val crashlyticsSubscriber = + FakeSessionSubscriber(sessionSubscriberName = SessionSubscriber.Name.CRASHLYTICS) + FirebaseSessionsDependencies.addDependency(SessionSubscriber.Name.CRASHLYTICS) + FirebaseSessionsDependencies.register(crashlyticsSubscriber) + } + + @After + fun cleanUp() { + FirebaseApp.clearInstancesForTest() + FirebaseSessionsDependencies.reset() + } + @Test fun logSession_populatesFid() = runTest { val fakeFirebaseApp = FakeFirebaseApp() @@ -61,13 +79,7 @@ class SessionFirelogPublisherTest { runCurrent() - System.out.println("FakeEventGDTLogger: $fakeEventGDTLogger") assertThat(fakeEventGDTLogger.loggedEvent!!.sessionData.firebaseInstallationId) .isEqualTo("FaKeFiD") } - - @After - fun cleanUp() { - FirebaseApp.clearInstancesForTest() - } } diff --git a/firebase-sessions/test-app/test-app.gradle.kts b/firebase-sessions/test-app/test-app.gradle.kts index 745b6de4411..276f9077052 100644 --- a/firebase-sessions/test-app/test-app.gradle.kts +++ b/firebase-sessions/test-app/test-app.gradle.kts @@ -52,11 +52,11 @@ dependencies { println("Using sessions released version: $latestReleasedVersion") // TODO(mrober): How to find the released versions of crashlytics and perf? implementation("com.google.firebase:firebase-crashlytics:18.4.3") - implementation("com.google.firebase:firebase-perf:20.4.1") + // implementation("com.google.firebase:firebase-perf:20.4.1") implementation("com.google.firebase:firebase-sessions:$latestReleasedVersion") } else { implementation(project(":firebase-crashlytics")) - implementation(project(":firebase-perf")) + // implementation(project(":firebase-perf")) implementation(project(":firebase-sessions")) } @@ -80,4 +80,4 @@ apply() configure { device("model=panther,version=33") // Pixel7 -} \ No newline at end of file +}