Skip to content

Integrate firebase-sessions with GoogleDataTransport #4851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions firebase-sessions/firebase-sessions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ dependencies {
implementation("com.google.firebase:firebase-encoders-json:18.0.1")
implementation("com.google.firebase:firebase-encoders:17.0.0")
implementation("com.google.firebase:firebase-installations-interop:17.1.0")
implementation("com.google.android.datatransport:transport-api:3.0.0")
implementation(libs.androidx.annotation)

runtimeOnly("com.google.firebase:firebase-installations:17.1.3")
runtimeOnly(project(":firebase-datatransport"))
Copy link
Contributor Author

@samedson samedson Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was required to make FireLog work

Before I had implementation("com.google.android.datatransport:transport-backend-cct:3.1.8")


testImplementation(libs.androidx.test.junit)
testImplementation(libs.androidx.test.runner)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.sessions

import com.google.android.datatransport.*
import com.google.android.datatransport.TransportFactory
import com.google.firebase.inject.Provider

/**
* The [EventGDTLoggerInterface] is for testing purposes so that we can mock EventGDTLogger in other
* classes that depend on it.
*
* @hide
*/
internal interface EventGDTLoggerInterface {
fun log(sessionEvent: SessionEvent)
}

/**
* The [EventGDTLogger] is responsible for encoding and logging events to the Google Data Transport
* library.
*
* @hide
*/
internal class EventGDTLogger(private val transportFactoryProvider: Provider<TransportFactory>) :
EventGDTLoggerInterface {

// Logs a [SessionEvent] to FireLog
override fun log(sessionEvent: SessionEvent) {
transportFactoryProvider
.get()
.getTransport(
EventGDTLogger.AQS_LOG_SOURCE,
SessionEvent::class.java,
Encoding.of("json"),
this::encode,
)
.send(Event.ofData(sessionEvent))
}

private fun encode(value: SessionEvent): ByteArray {
return SessionEvents.SESSION_EVENT_ENCODER.encode(value).toByteArray()
}

companion object {
// TODO What do we put for the AQS Log Source
private const val AQS_LOG_SOURCE = "FIREBASE_APPQUALITY_SESSION"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package com.google.firebase.sessions
import android.app.Application
import android.util.Log
import androidx.annotation.Discouraged
import com.google.android.datatransport.TransportFactory
import com.google.firebase.FirebaseApp
import com.google.firebase.inject.Provider
import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.app
Expand All @@ -29,10 +31,13 @@ class FirebaseSessions
internal constructor(
private val firebaseApp: FirebaseApp,
firebaseInstallations: FirebaseInstallationsApi,
backgroundDispatcher: CoroutineDispatcher
backgroundDispatcher: CoroutineDispatcher,
transportFactoryProvider: Provider<TransportFactory>,
) {
private val sessionGenerator = SessionGenerator(collectEvents = true)
private val sessionCoordinator = SessionCoordinator(firebaseInstallations, backgroundDispatcher)
private val eventGDTLogger = EventGDTLogger(transportFactoryProvider)
private val sessionCoordinator =
SessionCoordinator(firebaseInstallations, backgroundDispatcher, eventGDTLogger)

init {
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.sessions

/**
* The Firebase Sessions early initialization.
*
* @hide
*
* TODO: This should handle generating the Session ID and communicating with subscribers.
*/
class FirebaseSessionsEarly {}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
package com.google.firebase.sessions

import androidx.annotation.Keep
import com.google.android.datatransport.TransportFactory
import com.google.firebase.FirebaseApp
import com.google.firebase.annotations.concurrent.Background
import com.google.firebase.components.Component
import com.google.firebase.components.ComponentRegistrar
import com.google.firebase.components.Dependency
import com.google.firebase.components.*
import com.google.firebase.components.Qualified.qualified
import com.google.firebase.components.Qualified.unqualified
import com.google.firebase.installations.FirebaseInstallationsApi
Expand All @@ -35,29 +34,41 @@ import kotlinx.coroutines.CoroutineDispatcher
internal class FirebaseSessionsRegistrar : ComponentRegistrar {
override fun getComponents() =
listOf(
Component.builder(firebaseSessionsEarly)
.name(EARLY_LIBRARY_NAME)
.factory { _ -> FirebaseSessionsEarly() }
.eagerInDefaultApp()
.build(),
Component.builder(FirebaseSessions::class.java)
.name(LIBRARY_NAME)
.add(Dependency.required(firebaseSessionsEarly))
.add(Dependency.required(firebaseApp))
.add(Dependency.required(firebaseInstallationsApi))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.requiredProvider(transportFactory))
.factory { container ->
// Make sure FirebaseSessionsEarly has started up
container.get(firebaseSessionsEarly)
FirebaseSessions(
container.get(firebaseApp),
container.get(firebaseInstallationsApi),
container.get(backgroundDispatcher),
container.getProvider(transportFactory),
)
}
.eagerInDefaultApp()
.build(),
LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)
)

companion object {
private const val LIBRARY_NAME = "fire-sessions"
private const val EARLY_LIBRARY_NAME = "fire-sessions-early"

private val firebaseSessionsEarly = unqualified(FirebaseSessionsEarly::class.java)
private val firebaseApp = unqualified(FirebaseApp::class.java)
private val firebaseInstallationsApi = unqualified(FirebaseInstallationsApi::class.java)
private val backgroundDispatcher =
qualified(Background::class.java, CoroutineDispatcher::class.java)
private val transportFactory = unqualified(TransportFactory::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import kotlinx.coroutines.tasks.await
*/
internal class SessionCoordinator(
private val firebaseInstallations: FirebaseInstallationsApi,
backgroundDispatcher: CoroutineDispatcher
backgroundDispatcher: CoroutineDispatcher,
private val eventGDTLogger: EventGDTLoggerInterface,
) {
private val scope = CoroutineScope(backgroundDispatcher)

Expand All @@ -46,7 +47,13 @@ internal class SessionCoordinator(
""
}

Log.i(TAG, "Initiate session start: $sessionEvent")
try {
eventGDTLogger.log(sessionEvent)

Log.i(TAG, "Logged Session Start event: $sessionEvent")
} catch (e: RuntimeException) {
Log.w(TAG, "Failed to log Session Start event: ", e)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.sessions

import com.google.android.datatransport.Encoding
import com.google.android.datatransport.Event
import com.google.android.datatransport.TransportFactory
import com.google.common.truth.Truth.assertThat
import com.google.firebase.FirebaseApp
import com.google.firebase.sessions.testing.FakeFirebaseApp
import com.google.firebase.sessions.testing.FakeProvider
import com.google.firebase.sessions.testing.FakeTransportFactory
import com.google.firebase.sessions.testing.TestSessionEventData
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class EventGDTLoggerTest {

@Test
fun event_logsToGoogleDataTransport() {
val sessionEvent =
SessionEvents.startSession(
FakeFirebaseApp.fakeFirebaseApp(),
TestSessionEventData.TEST_SESSION_DETAILS
)
val fakeTransportFactory = FakeTransportFactory()
val fakeTransportFactoryProvider = FakeProvider(fakeTransportFactory as TransportFactory)
val eventGDTLogger = EventGDTLogger(transportFactoryProvider = fakeTransportFactoryProvider)

eventGDTLogger.log(sessionEvent = sessionEvent)

assertThat(fakeTransportFactory.name).isEqualTo("FIREBASE_APPQUALITY_SESSION")
assertThat(fakeTransportFactory.payloadEncoding).isEqualTo(Encoding.of("json"))
assertThat(fakeTransportFactory.fakeTransport!!.sentEvent)
.isEqualTo(Event.ofData(TestSessionEventData.EXPECTED_DEFAULT_SESSION_EVENT))
}

@After
fun cleanUp() {
FirebaseApp.clearInstancesForTest()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.firebase.sessions

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.google.firebase.sessions.testing.FakeEventGDTLogger
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
Expand All @@ -31,10 +32,12 @@ import org.junit.runner.RunWith
class SessionCoordinatorTest {
@Test
fun attemptLoggingSessionEvent_populatesFid() = runTest {
val fakeEventGDTLogger = FakeEventGDTLogger()
val sessionCoordinator =
SessionCoordinator(
firebaseInstallations = FakeFirebaseInstallations("FaKeFiD"),
backgroundDispatcher = StandardTestDispatcher(testScheduler),
eventGDTLogger = fakeEventGDTLogger,
)

// Construct an event with no fid set.
Expand Down Expand Up @@ -62,5 +65,7 @@ class SessionCoordinatorTest {
runCurrent()

assertThat(sessionEvent.sessionData.firebaseInstallationId).isEqualTo("FaKeFiD")
assertThat(fakeEventGDTLogger.loggedEvent!!.sessionData.firebaseInstallationId)
.isEqualTo("FaKeFiD")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

package com.google.firebase.sessions

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.google.firebase.FirebaseApp
import com.google.firebase.sessions.testing.FakeFirebaseApp
import com.google.firebase.sessions.testing.TestSessionEventData
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -30,38 +29,13 @@ import org.robolectric.RobolectricTestRunner
class SessionEventTest {
@Test
fun sessionStart_populatesSessionDetailsCorrectly() {
val sessionDetails =
SessionDetails(
sessionId = "a1b2c3",
firstSessionId = "a1a1a1",
collectEvents = true,
sessionIndex = 3,
val sessionEvent =
SessionEvents.startSession(
FakeFirebaseApp.fakeFirebaseApp(),
TestSessionEventData.TEST_SESSION_DETAILS
)
val sessionEvent = SessionEvents.startSession(FakeFirebaseApp.fakeFirebaseApp(), sessionDetails)

assertThat(sessionEvent)
.isEqualTo(
SessionEvent(
eventType = EventType.SESSION_START,
sessionData =
SessionInfo(
sessionId = "a1b2c3",
firstSessionId = "a1a1a1",
sessionIndex = 3,
),
applicationInfo =
ApplicationInfo(
appId = FakeFirebaseApp.MOCK_APP_ID,
deviceModel = "",
sessionSdkVersion = BuildConfig.VERSION_NAME,
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
AndroidApplicationInfo(
packageName = ApplicationProvider.getApplicationContext<Context>().packageName,
versionName = FakeFirebaseApp.MOCK_APP_VERSION
),
)
)
)
assertThat(sessionEvent).isEqualTo(TestSessionEventData.EXPECTED_DEFAULT_SESSION_EVENT)
}

@After
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.sessions.testing

import com.google.firebase.sessions.EventGDTLogger
import com.google.firebase.sessions.EventGDTLoggerInterface
import com.google.firebase.sessions.SessionEvent

/**
* The [FakeEventGDTLogger] is for mocking [EventGDTLogger].
*
* @hide
*/
internal class FakeEventGDTLogger : EventGDTLoggerInterface {

// Lets tests assert the right event was logged.
var loggedEvent: SessionEvent? = null

override fun log(sessionEvent: SessionEvent) {
loggedEvent = sessionEvent
}
}
Loading