Skip to content

Commit 09d35f6

Browse files
authored
Populate firebaseInstallationId field in session (#4801)
1 parent ac3d738 commit 09d35f6

File tree

10 files changed

+204
-11
lines changed

10 files changed

+204
-11
lines changed

firebase-sessions/firebase-sessions.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ dependencies {
5454
testImplementation(libs.androidx.test.junit)
5555
testImplementation(libs.androidx.test.runner)
5656
testImplementation(libs.junit)
57+
testImplementation(libs.kotlin.coroutines.test)
5758
testImplementation(libs.robolectric)
5859
testImplementation(libs.truth)
5960

firebase-sessions/src/androidTest/kotlin/com/google/firebase/sessions/FirebaseSessionsTests.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ class FirebaseSessionsTests {
3939
fun setUp() {
4040
Firebase.initialize(
4141
ApplicationProvider.getApplicationContext(),
42-
FirebaseOptions.Builder().setApplicationId("APP_ID").build()
42+
FirebaseOptions.Builder()
43+
.setApplicationId(APP_ID)
44+
.setApiKey(API_KEY)
45+
.setProjectId(PROJECT_ID)
46+
.build()
4347
)
4448
}
4549

@@ -53,4 +57,10 @@ class FirebaseSessionsTests {
5357
// This will be replaced with real tests.
5458
assertThat(FirebaseSessions.instance.greeting()).isEqualTo("Matt says hi!")
5559
}
60+
61+
companion object {
62+
private const val APP_ID = "1:1:android:1a"
63+
private const val API_KEY = "API-KEY-API-KEY-API-KEY-API-KEY-API-KEY"
64+
private const val PROJECT_ID = "PROJECT-ID"
65+
}
5666
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ import com.google.firebase.FirebaseApp
2323
import com.google.firebase.installations.FirebaseInstallationsApi
2424
import com.google.firebase.ktx.Firebase
2525
import com.google.firebase.ktx.app
26+
import kotlinx.coroutines.CoroutineDispatcher
2627

2728
class FirebaseSessions
28-
internal constructor(firebaseApp: FirebaseApp, firebaseInstallations: FirebaseInstallationsApi) {
29+
internal constructor(
30+
firebaseApp: FirebaseApp,
31+
firebaseInstallations: FirebaseInstallationsApi,
32+
backgroundDispatcher: CoroutineDispatcher
33+
) {
2934
private val sessionGenerator = SessionGenerator(collectEvents = true)
35+
private val sessionCoordinator = SessionCoordinator(firebaseInstallations, backgroundDispatcher)
3036

3137
init {
3238
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
@@ -36,7 +42,6 @@ internal constructor(firebaseApp: FirebaseApp, firebaseInstallations: FirebaseIn
3642
} else {
3743
Log.w(TAG, "Failed to register lifecycle callbacks, unexpected context ${context.javaClass}.")
3844
}
39-
Log.i(TAG, "Firebase Installations ID: ${firebaseInstallations.id}")
4045
}
4146

4247
@Discouraged(message = "This will be replaced with a real API.")
@@ -46,7 +51,9 @@ internal constructor(firebaseApp: FirebaseApp, firebaseInstallations: FirebaseIn
4651
val sessionDetails = sessionGenerator.generateNewSession()
4752
val sessionEvent = SessionEvents.startSession(sessionDetails)
4853

49-
Log.i(TAG, "Initiate session start: $sessionEvent")
54+
if (sessionDetails.collectEvents) {
55+
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
56+
}
5057
}
5158

5259
companion object {

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
@@ -16,11 +16,15 @@ package com.google.firebase.sessions
1616

1717
import androidx.annotation.Keep
1818
import com.google.firebase.FirebaseApp
19+
import com.google.firebase.annotations.concurrent.Background
1920
import com.google.firebase.components.Component
2021
import com.google.firebase.components.ComponentRegistrar
2122
import com.google.firebase.components.Dependency
23+
import com.google.firebase.components.Qualified.qualified
24+
import com.google.firebase.components.Qualified.unqualified
2225
import com.google.firebase.installations.FirebaseInstallationsApi
2326
import com.google.firebase.platforminfo.LibraryVersionComponent
27+
import kotlinx.coroutines.CoroutineDispatcher
2428

2529
/**
2630
* [ComponentRegistrar] for setting up [FirebaseSessions].
@@ -33,12 +37,14 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar {
3337
listOf(
3438
Component.builder(FirebaseSessions::class.java)
3539
.name(LIBRARY_NAME)
36-
.add(Dependency.required(FirebaseApp::class.java))
37-
.add(Dependency.required(FirebaseInstallationsApi::class.java))
40+
.add(Dependency.required(firebaseApp))
41+
.add(Dependency.required(firebaseInstallationsApi))
42+
.add(Dependency.required(backgroundDispatcher))
3843
.factory { container ->
3944
FirebaseSessions(
40-
container.get(FirebaseApp::class.java),
41-
container.get(FirebaseInstallationsApi::class.java)
45+
container.get(firebaseApp),
46+
container.get(firebaseInstallationsApi),
47+
container.get(backgroundDispatcher),
4248
)
4349
}
4450
.eagerInDefaultApp()
@@ -48,5 +54,10 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar {
4854

4955
companion object {
5056
private const val LIBRARY_NAME = "fire-sessions"
57+
58+
private val firebaseApp = unqualified(FirebaseApp::class.java)
59+
private val firebaseInstallationsApi = unqualified(FirebaseInstallationsApi::class.java)
60+
private val backgroundDispatcher =
61+
qualified(Background::class.java, CoroutineDispatcher::class.java)
5162
}
5263
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 android.util.Log
20+
import com.google.firebase.installations.FirebaseInstallationsApi
21+
import kotlinx.coroutines.CoroutineDispatcher
22+
import kotlinx.coroutines.CoroutineScope
23+
import kotlinx.coroutines.launch
24+
import kotlinx.coroutines.tasks.await
25+
26+
/**
27+
* [SessionCoordinator] is responsible for coordinating the systems in this SDK involved with
28+
* sending a [SessionEvent].
29+
*
30+
* @hide
31+
*/
32+
internal class SessionCoordinator(
33+
private val firebaseInstallations: FirebaseInstallationsApi,
34+
backgroundDispatcher: CoroutineDispatcher
35+
) {
36+
private val scope = CoroutineScope(backgroundDispatcher)
37+
38+
fun attemptLoggingSessionEvent(sessionEvent: SessionEvent) =
39+
scope.launch {
40+
sessionEvent.sessionData.firebaseInstallationId =
41+
try {
42+
firebaseInstallations.id.await()
43+
} catch (ex: Exception) {
44+
Log.w(TAG, "Session Installations Error", ex)
45+
// Use an empty fid if there is any failure.
46+
""
47+
}
48+
49+
Log.i(TAG, "Initiate session start: $sessionEvent")
50+
}
51+
52+
companion object {
53+
private const val TAG = "SessionCoordinator"
54+
}
55+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ internal data class SessionInfo(
5555

5656
/** What order this Session came in this run of the app. For the first Session this will be 0. */
5757
val sessionIndex: Int,
58+
59+
/** Identifies a unique device+app installation: go/firebase-installations */
60+
var firebaseInstallationId: String = "",
5861
)

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,20 @@ internal object SessionEvents {
4848
ctx.add(FieldDescriptor.of("session_id"), sessionInfo.sessionId)
4949
ctx.add(FieldDescriptor.of("first_session_id"), sessionInfo.firstSessionId)
5050
ctx.add(FieldDescriptor.of("session_index"), sessionInfo.sessionIndex)
51+
ctx.add(
52+
FieldDescriptor.of("firebase_installation_id"),
53+
sessionInfo.firebaseInstallationId
54+
)
5155
}
5256
}
5357
}
5458
.build()
5559

56-
/** Construct a Session Start event */
60+
/**
61+
* Construct a Session Start event.
62+
*
63+
* Some mutable fields, e.g. firebaseInstallationId, get populated later.
64+
*/
5765
fun startSession(sessionDetails: SessionDetails) =
5866
SessionEvent(
5967
eventType = EventType.SESSION_START,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 androidx.test.ext.junit.runners.AndroidJUnit4
20+
import com.google.common.truth.Truth.assertThat
21+
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
22+
import kotlinx.coroutines.ExperimentalCoroutinesApi
23+
import kotlinx.coroutines.test.StandardTestDispatcher
24+
import kotlinx.coroutines.test.runCurrent
25+
import kotlinx.coroutines.test.runTest
26+
import org.junit.Test
27+
import org.junit.runner.RunWith
28+
29+
@OptIn(ExperimentalCoroutinesApi::class)
30+
@RunWith(AndroidJUnit4::class)
31+
class SessionCoordinatorTest {
32+
@Test
33+
fun attemptLoggingSessionEvent_populatesFid() = runTest {
34+
val sessionCoordinator =
35+
SessionCoordinator(
36+
firebaseInstallations = FakeFirebaseInstallations("FaKeFiD"),
37+
backgroundDispatcher = StandardTestDispatcher(testScheduler),
38+
)
39+
40+
// Construct an event with no fid set.
41+
val sessionEvent =
42+
SessionEvent(
43+
eventType = EventType.SESSION_START,
44+
sessionData =
45+
SessionInfo(
46+
sessionId = "id",
47+
firstSessionId = "first",
48+
sessionIndex = 3,
49+
),
50+
)
51+
52+
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
53+
54+
runCurrent()
55+
56+
assertThat(sessionEvent.sessionData.firebaseInstallationId).isEqualTo("FaKeFiD")
57+
}
58+
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class SessionEventEncoderTest {
3434
sessionId = "id",
3535
firstSessionId = "first",
3636
sessionIndex = 9,
37+
firebaseInstallationId = "fid"
3738
),
3839
)
3940

@@ -47,7 +48,8 @@ class SessionEventEncoderTest {
4748
"session_data":{
4849
"session_id":"id",
4950
"first_session_id":"first",
50-
"session_index":9
51+
"session_index":9,
52+
"firebase_installation_id":"fid"
5153
}
5254
}
5355
"""
@@ -79,7 +81,8 @@ class SessionEventEncoderTest {
7981
"session_data":{
8082
"session_id":"",
8183
"first_session_id":"",
82-
"session_index":0
84+
"session_index":0,
85+
"firebase_installation_id":""
8386
}
8487
}
8588
"""
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.android.gms.tasks.Task
20+
import com.google.android.gms.tasks.Tasks
21+
import com.google.firebase.installations.FirebaseInstallationsApi
22+
import com.google.firebase.installations.InstallationTokenResult
23+
import com.google.firebase.installations.internal.FidListener
24+
import com.google.firebase.installations.internal.FidListenerHandle
25+
26+
/** Fake [FirebaseInstallationsApi] that implements [getId] and always returns the given [fid]. */
27+
internal class FakeFirebaseInstallations(private val fid: String = "") : FirebaseInstallationsApi {
28+
override fun getId(): Task<String> = Tasks.forResult(fid)
29+
30+
override fun getToken(forceRefresh: Boolean): Task<InstallationTokenResult> =
31+
throw NotImplementedError("getToken not faked.")
32+
33+
override fun delete(): Task<Void> = throw NotImplementedError("delete not faked.")
34+
35+
override fun registerFidListener(listener: FidListener): FidListenerHandle =
36+
throw NotImplementedError("registerFidListener not faked.")
37+
}

0 commit comments

Comments
 (0)