Skip to content

Commit 34c44fc

Browse files
committed
Add GDT to Firebase Sessions
1 parent e7050b3 commit 34c44fc

File tree

13 files changed

+371
-40
lines changed

13 files changed

+371
-40
lines changed

firebase-sessions/firebase-sessions.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ dependencies {
4747
implementation("com.google.firebase:firebase-encoders-json:18.0.1")
4848
implementation("com.google.firebase:firebase-encoders:17.0.0")
4949
implementation("com.google.firebase:firebase-installations-interop:17.1.0")
50+
implementation("com.google.android.datatransport:transport-api:3.0.0")
5051
implementation(libs.androidx.annotation)
5152

5253
runtimeOnly("com.google.firebase:firebase-installations:17.1.3")
54+
runtimeOnly(project(":firebase-datatransport"))
5355

5456
testImplementation(libs.androidx.test.junit)
5557
testImplementation(libs.androidx.test.runner)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
import com.google.android.datatransport.*
20+
import com.google.android.datatransport.TransportFactory
21+
import com.google.firebase.inject.Provider
22+
23+
/**
24+
* The [EventGDTLoggerInterface] is for testing purposes so that we can mock EventGDTLogger in other
25+
* classes that depend on it.
26+
*
27+
* @hide
28+
*/
29+
internal interface EventGDTLoggerInterface {
30+
fun log(sessionEvent: SessionEvent)
31+
}
32+
33+
/**
34+
* The [EventGDTLogger] is responsible for encoding and logging events to the Google Data Transport
35+
* library.
36+
*
37+
* @hide
38+
*/
39+
internal class EventGDTLogger(private val transportFactoryProvider: Provider<TransportFactory>) :
40+
EventGDTLoggerInterface {
41+
42+
// Logs a [SessionEvent] to FireLog
43+
override fun log(sessionEvent: SessionEvent) {
44+
transportFactoryProvider
45+
.get()
46+
.getTransport(
47+
EventGDTLogger.AQS_LOG_SOURCE,
48+
SessionEvent::class.java,
49+
Encoding.of("json"),
50+
this::encode,
51+
)
52+
.send(Event.ofData(sessionEvent))
53+
}
54+
55+
private fun encode(value: SessionEvent): ByteArray {
56+
return SessionEvents.SESSION_EVENT_ENCODER.encode(value).toByteArray()
57+
}
58+
59+
companion object {
60+
// TODO What do we put for the AQS Log Source
61+
private const val AQS_LOG_SOURCE = "FIREBASE_APPQUALITY_SESSION"
62+
}
63+
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessions.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package com.google.firebase.sessions
1919
import android.app.Application
2020
import android.util.Log
2121
import androidx.annotation.Discouraged
22+
import com.google.android.datatransport.TransportFactory
2223
import com.google.firebase.FirebaseApp
24+
import com.google.firebase.inject.Provider
2325
import com.google.firebase.installations.FirebaseInstallationsApi
2426
import com.google.firebase.ktx.Firebase
2527
import com.google.firebase.ktx.app
@@ -29,10 +31,13 @@ class FirebaseSessions
2931
internal constructor(
3032
private val firebaseApp: FirebaseApp,
3133
firebaseInstallations: FirebaseInstallationsApi,
32-
backgroundDispatcher: CoroutineDispatcher
34+
backgroundDispatcher: CoroutineDispatcher,
35+
transportFactoryProvider: Provider<TransportFactory>,
3336
) {
3437
private val sessionGenerator = SessionGenerator(collectEvents = true)
35-
private val sessionCoordinator = SessionCoordinator(firebaseInstallations, backgroundDispatcher)
38+
private val eventGDTLogger = EventGDTLogger(transportFactoryProvider)
39+
private val sessionCoordinator =
40+
SessionCoordinator(firebaseInstallations, backgroundDispatcher, eventGDTLogger)
3641

3742
init {
3843
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
/**
20+
* The Firebase Sessions early initialization.
21+
*
22+
* @hide
23+
*
24+
* TODO: This should handle generating the Session ID and communicating with subscribers.
25+
*/
26+
class FirebaseSessionsEarly {}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsRegistrar.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
package com.google.firebase.sessions
1616

1717
import androidx.annotation.Keep
18+
import com.google.android.datatransport.TransportFactory
1819
import com.google.firebase.FirebaseApp
1920
import com.google.firebase.annotations.concurrent.Background
20-
import com.google.firebase.components.Component
21-
import com.google.firebase.components.ComponentRegistrar
22-
import com.google.firebase.components.Dependency
21+
import com.google.firebase.components.*
2322
import com.google.firebase.components.Qualified.qualified
2423
import com.google.firebase.components.Qualified.unqualified
2524
import com.google.firebase.installations.FirebaseInstallationsApi
@@ -35,29 +34,41 @@ import kotlinx.coroutines.CoroutineDispatcher
3534
internal class FirebaseSessionsRegistrar : ComponentRegistrar {
3635
override fun getComponents() =
3736
listOf(
37+
Component.builder(firebaseSessionsEarly)
38+
.name(EARLY_LIBRARY_NAME)
39+
.factory { _ -> FirebaseSessionsEarly() }
40+
.eagerInDefaultApp()
41+
.build(),
3842
Component.builder(FirebaseSessions::class.java)
3943
.name(LIBRARY_NAME)
44+
.add(Dependency.required(firebaseSessionsEarly))
4045
.add(Dependency.required(firebaseApp))
4146
.add(Dependency.required(firebaseInstallationsApi))
4247
.add(Dependency.required(backgroundDispatcher))
48+
.add(Dependency.requiredProvider(transportFactory))
4349
.factory { container ->
50+
// Make sure FirebaseSessionsEarly has started up
51+
container.get(firebaseSessionsEarly)
4452
FirebaseSessions(
4553
container.get(firebaseApp),
4654
container.get(firebaseInstallationsApi),
4755
container.get(backgroundDispatcher),
56+
container.getProvider(transportFactory),
4857
)
4958
}
50-
.eagerInDefaultApp()
5159
.build(),
5260
LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME)
5361
)
5462

5563
companion object {
5664
private const val LIBRARY_NAME = "fire-sessions"
65+
private const val EARLY_LIBRARY_NAME = "fire-sessions-early"
5766

67+
private val firebaseSessionsEarly = unqualified(FirebaseSessionsEarly::class.java)
5868
private val firebaseApp = unqualified(FirebaseApp::class.java)
5969
private val firebaseInstallationsApi = unqualified(FirebaseInstallationsApi::class.java)
6070
private val backgroundDispatcher =
6171
qualified(Background::class.java, CoroutineDispatcher::class.java)
72+
private val transportFactory = unqualified(TransportFactory::class.java)
6273
}
6374
}

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SessionCoordinator.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import kotlinx.coroutines.tasks.await
3131
*/
3232
internal class SessionCoordinator(
3333
private val firebaseInstallations: FirebaseInstallationsApi,
34-
backgroundDispatcher: CoroutineDispatcher
34+
backgroundDispatcher: CoroutineDispatcher,
35+
private val eventGDTLogger: EventGDTLoggerInterface,
3536
) {
3637
private val scope = CoroutineScope(backgroundDispatcher)
3738

@@ -46,7 +47,13 @@ internal class SessionCoordinator(
4647
""
4748
}
4849

49-
Log.i(TAG, "Initiate session start: $sessionEvent")
50+
try {
51+
eventGDTLogger.log(sessionEvent)
52+
53+
Log.i(TAG, "Logged Session Start event: $sessionEvent")
54+
} catch (e: RuntimeException) {
55+
Log.w(TAG, "Failed to log Session Start event: ", e)
56+
}
5057
}
5158

5259
companion object {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
import com.google.android.datatransport.Encoding
20+
import com.google.android.datatransport.Event
21+
import com.google.android.datatransport.TransportFactory
22+
import com.google.common.truth.Truth.assertThat
23+
import com.google.firebase.FirebaseApp
24+
import com.google.firebase.sessions.testing.FakeFirebaseApp
25+
import com.google.firebase.sessions.testing.FakeProvider
26+
import com.google.firebase.sessions.testing.FakeTransportFactory
27+
import com.google.firebase.sessions.testing.TestSessionEventData
28+
import org.junit.After
29+
import org.junit.Test
30+
import org.junit.runner.RunWith
31+
import org.robolectric.RobolectricTestRunner
32+
33+
@RunWith(RobolectricTestRunner::class)
34+
class EventGDTLoggerTest {
35+
36+
@Test
37+
fun event_logsToGoogleDataTransport() {
38+
val sessionEvent =
39+
SessionEvents.startSession(
40+
FakeFirebaseApp.fakeFirebaseApp(),
41+
TestSessionEventData.TEST_SESSION_DETAILS
42+
)
43+
val fakeTransportFactory = FakeTransportFactory()
44+
val fakeTransportFactoryProvider = FakeProvider(fakeTransportFactory as TransportFactory)
45+
val eventGDTLogger = EventGDTLogger(transportFactoryProvider = fakeTransportFactoryProvider)
46+
47+
eventGDTLogger.log(sessionEvent = sessionEvent)
48+
49+
assertThat(fakeTransportFactory.name).isEqualTo("FIREBASE_APPQUALITY_SESSION")
50+
assertThat(fakeTransportFactory.payloadEncoding).isEqualTo(Encoding.of("json"))
51+
assertThat(fakeTransportFactory.fakeTransport!!.sentEvent)
52+
.isEqualTo(Event.ofData(TestSessionEventData.EXPECTED_DEFAULT_SESSION_EVENT))
53+
}
54+
55+
@After
56+
fun cleanUp() {
57+
FirebaseApp.clearInstancesForTest()
58+
}
59+
}

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionCoordinatorTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.google.firebase.sessions
1818

1919
import androidx.test.ext.junit.runners.AndroidJUnit4
2020
import com.google.common.truth.Truth.assertThat
21+
import com.google.firebase.sessions.testing.FakeEventGDTLogger
2122
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
2223
import kotlinx.coroutines.ExperimentalCoroutinesApi
2324
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -31,10 +32,12 @@ import org.junit.runner.RunWith
3132
class SessionCoordinatorTest {
3233
@Test
3334
fun attemptLoggingSessionEvent_populatesFid() = runTest {
35+
val fakeEventGDTLogger = FakeEventGDTLogger()
3436
val sessionCoordinator =
3537
SessionCoordinator(
3638
firebaseInstallations = FakeFirebaseInstallations("FaKeFiD"),
3739
backgroundDispatcher = StandardTestDispatcher(testScheduler),
40+
eventGDTLogger = fakeEventGDTLogger,
3841
)
3942

4043
// Construct an event with no fid set.
@@ -62,5 +65,7 @@ class SessionCoordinatorTest {
6265
runCurrent()
6366

6467
assertThat(sessionEvent.sessionData.firebaseInstallationId).isEqualTo("FaKeFiD")
68+
assertThat(fakeEventGDTLogger.loggedEvent!!.sessionData.firebaseInstallationId)
69+
.isEqualTo("FaKeFiD")
6570
}
6671
}

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionEventTest.kt

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
package com.google.firebase.sessions
1818

19-
import android.content.Context
20-
import androidx.test.core.app.ApplicationProvider
2119
import com.google.common.truth.Truth.assertThat
2220
import com.google.firebase.FirebaseApp
2321
import com.google.firebase.sessions.testing.FakeFirebaseApp
22+
import com.google.firebase.sessions.testing.TestSessionEventData
2423
import org.junit.After
2524
import org.junit.Test
2625
import org.junit.runner.RunWith
@@ -30,38 +29,13 @@ import org.robolectric.RobolectricTestRunner
3029
class SessionEventTest {
3130
@Test
3231
fun sessionStart_populatesSessionDetailsCorrectly() {
33-
val sessionDetails =
34-
SessionDetails(
35-
sessionId = "a1b2c3",
36-
firstSessionId = "a1a1a1",
37-
collectEvents = true,
38-
sessionIndex = 3,
32+
val sessionEvent =
33+
SessionEvents.startSession(
34+
FakeFirebaseApp.fakeFirebaseApp(),
35+
TestSessionEventData.TEST_SESSION_DETAILS
3936
)
40-
val sessionEvent = SessionEvents.startSession(FakeFirebaseApp.fakeFirebaseApp(), sessionDetails)
4137

42-
assertThat(sessionEvent)
43-
.isEqualTo(
44-
SessionEvent(
45-
eventType = EventType.SESSION_START,
46-
sessionData =
47-
SessionInfo(
48-
sessionId = "a1b2c3",
49-
firstSessionId = "a1a1a1",
50-
sessionIndex = 3,
51-
),
52-
applicationInfo =
53-
ApplicationInfo(
54-
appId = FakeFirebaseApp.MOCK_APP_ID,
55-
deviceModel = "",
56-
sessionSdkVersion = BuildConfig.VERSION_NAME,
57-
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
58-
AndroidApplicationInfo(
59-
packageName = ApplicationProvider.getApplicationContext<Context>().packageName,
60-
versionName = FakeFirebaseApp.MOCK_APP_VERSION
61-
),
62-
)
63-
)
64-
)
38+
assertThat(sessionEvent).isEqualTo(TestSessionEventData.EXPECTED_DEFAULT_SESSION_EVENT)
6539
}
6640

6741
@After
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions.testing
18+
19+
import com.google.firebase.sessions.EventGDTLogger
20+
import com.google.firebase.sessions.EventGDTLoggerInterface
21+
import com.google.firebase.sessions.SessionEvent
22+
23+
/**
24+
* The [FakeEventGDTLogger] is for mocking [EventGDTLogger].
25+
*
26+
* @hide
27+
*/
28+
internal class FakeEventGDTLogger : EventGDTLoggerInterface {
29+
30+
// Lets tests assert the right event was logged.
31+
var loggedEvent: SessionEvent? = null
32+
33+
override fun log(sessionEvent: SessionEvent) {
34+
loggedEvent = sessionEvent
35+
}
36+
}

0 commit comments

Comments
 (0)