Skip to content

Commit c8e6b4c

Browse files
authored
Package session start event's application information. (#4829)
1 parent 09d35f6 commit c8e6b4c

File tree

9 files changed

+247
-9
lines changed

9 files changed

+247
-9
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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.firebase.FirebaseApp
20+
import com.google.firebase.encoders.json.NumberedEnum
21+
22+
/** Enum denoting different development environments. */
23+
internal enum class LogEnvironment(override val number: Int) : NumberedEnum {
24+
/** Unknown environment. */
25+
LOG_ENVIRONMENT_UNKNOWN(0),
26+
27+
/** Autopush environment. */
28+
LOG_ENVIRONMENT_AUTOPUSH(1),
29+
30+
/** Staging environment. */
31+
LOG_ENVIRONMENT_STAGING(2),
32+
33+
/** Production environment. */
34+
LOG_ENVIRONMENT_PROD(3),
35+
}
36+
37+
internal data class AndroidApplicationInfo(
38+
/** The package name of the application/bundle name. */
39+
val packageName: String,
40+
41+
/** The version of the application. */
42+
val versionName: String,
43+
)
44+
45+
internal data class ApplicationInfo(
46+
/** The Firebase Application ID of the application. */
47+
val appId: String,
48+
49+
/** The model of the device that runs the application. */
50+
val deviceModel: String,
51+
52+
/** The SDK version of the sessions library. */
53+
val sessionSdkVersion: String,
54+
55+
/** The logging environment for the events. */
56+
val logEnvironment: LogEnvironment,
57+
58+
/** The android application information for the app. */
59+
val androidAppInfo: AndroidApplicationInfo,
60+
)
61+
62+
internal fun getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo {
63+
val packageName = firebaseApp.applicationContext.packageName
64+
val packageInfo = firebaseApp.applicationContext.packageManager.getPackageInfo(packageName, 0)
65+
66+
return ApplicationInfo(
67+
appId = firebaseApp.options.applicationId,
68+
deviceModel = "",
69+
sessionSdkVersion = BuildConfig.VERSION_NAME,
70+
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
71+
androidAppInfo =
72+
AndroidApplicationInfo(packageName = packageName, versionName = packageInfo.versionName)
73+
)
74+
}

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import kotlinx.coroutines.CoroutineDispatcher
2727

2828
class FirebaseSessions
2929
internal constructor(
30-
firebaseApp: FirebaseApp,
30+
private val firebaseApp: FirebaseApp,
3131
firebaseInstallations: FirebaseInstallationsApi,
3232
backgroundDispatcher: CoroutineDispatcher
3333
) {
@@ -36,11 +36,14 @@ internal constructor(
3636

3737
init {
3838
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
39-
val context = firebaseApp.applicationContext.applicationContext
40-
if (context is Application) {
41-
context.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)
39+
val appContext = firebaseApp.applicationContext.applicationContext
40+
if (appContext is Application) {
41+
appContext.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)
4242
} else {
43-
Log.w(TAG, "Failed to register lifecycle callbacks, unexpected context ${context.javaClass}.")
43+
Log.w(
44+
TAG,
45+
"Failed to register lifecycle callbacks, unexpected context ${appContext.javaClass}."
46+
)
4447
}
4548
}
4649

@@ -49,7 +52,7 @@ internal constructor(
4952

5053
private fun initiateSessionStart() {
5154
val sessionDetails = sessionGenerator.generateNewSession()
52-
val sessionEvent = SessionEvents.startSession(sessionDetails)
55+
val sessionEvent = SessionEvents.startSession(firebaseApp, sessionDetails)
5356

5457
if (sessionDetails.collectEvents) {
5558
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)

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
@@ -31,6 +31,9 @@ internal data class SessionEvent(
3131

3232
/** Information about the session triggering the event. */
3333
val sessionData: SessionInfo,
34+
35+
/** Information about the application that is generating the session events. */
36+
val applicationInfo: ApplicationInfo,
3437
)
3538

3639
/** Enum denoting all possible session event types. */

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.firebase.sessions
1818

19+
import com.google.firebase.FirebaseApp
1920
import com.google.firebase.encoders.DataEncoder
2021
import com.google.firebase.encoders.FieldDescriptor
2122
import com.google.firebase.encoders.ObjectEncoderContext
@@ -62,7 +63,7 @@ internal object SessionEvents {
6263
*
6364
* Some mutable fields, e.g. firebaseInstallationId, get populated later.
6465
*/
65-
fun startSession(sessionDetails: SessionDetails) =
66+
fun startSession(firebaseApp: FirebaseApp, sessionDetails: SessionDetails) =
6667
SessionEvent(
6768
eventType = EventType.SESSION_START,
6869
sessionData =
@@ -71,5 +72,6 @@ internal object SessionEvents {
7172
sessionDetails.firstSessionId,
7273
sessionDetails.sessionIndex,
7374
),
75+
applicationInfo = getApplicationInfo(firebaseApp)
7476
)
7577
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.content.Context
20+
import androidx.test.core.app.ApplicationProvider
21+
import com.google.common.truth.Truth.assertThat
22+
import com.google.firebase.FirebaseApp
23+
import com.google.firebase.sessions.testing.FakeFirebaseApp
24+
import org.junit.After
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.robolectric.RobolectricTestRunner
28+
29+
@RunWith(RobolectricTestRunner::class)
30+
class ApplicationInfoTest {
31+
32+
@Test
33+
fun applicationInfo_populatesInfoCorrectly() {
34+
val applicationInfo = getApplicationInfo(FakeFirebaseApp.fakeFirebaseApp())
35+
assertThat(applicationInfo)
36+
.isEqualTo(
37+
ApplicationInfo(
38+
appId = FakeFirebaseApp.MOCK_APP_ID,
39+
deviceModel = "",
40+
sessionSdkVersion = BuildConfig.VERSION_NAME,
41+
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
42+
AndroidApplicationInfo(
43+
packageName = ApplicationProvider.getApplicationContext<Context>().packageName,
44+
versionName = FakeFirebaseApp.MOCK_APP_VERSION
45+
)
46+
)
47+
)
48+
}
49+
50+
@After
51+
fun cleanUp() {
52+
FirebaseApp.clearInstancesForTest()
53+
}
54+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ class SessionCoordinatorTest {
4747
firstSessionId = "first",
4848
sessionIndex = 3,
4949
),
50+
applicationInfo =
51+
ApplicationInfo(
52+
appId = "",
53+
deviceModel = "",
54+
sessionSdkVersion = "",
55+
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
56+
androidAppInfo = AndroidApplicationInfo(packageName = "", versionName = "")
57+
)
5058
)
5159

5260
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ class SessionEventEncoderTest {
3636
sessionIndex = 9,
3737
firebaseInstallationId = "fid"
3838
),
39+
applicationInfo =
40+
ApplicationInfo(
41+
appId = "",
42+
deviceModel = "",
43+
sessionSdkVersion = "",
44+
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
45+
AndroidApplicationInfo(packageName = "", versionName = ""),
46+
)
3947
)
4048

4149
val json = SESSION_EVENT_ENCODER.encode(sessionEvent)
@@ -69,6 +77,14 @@ class SessionEventEncoderTest {
6977
firstSessionId = "",
7078
sessionIndex = 0,
7179
),
80+
applicationInfo =
81+
ApplicationInfo(
82+
appId = "",
83+
deviceModel = "",
84+
sessionSdkVersion = "",
85+
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
86+
AndroidApplicationInfo(packageName = "", versionName = ""),
87+
)
7288
)
7389

7490
val json = SESSION_EVENT_ENCODER.encode(sessionEvent)

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@
1616

1717
package com.google.firebase.sessions
1818

19+
import android.content.Context
20+
import androidx.test.core.app.ApplicationProvider
1921
import com.google.common.truth.Truth.assertThat
22+
import com.google.firebase.FirebaseApp
23+
import com.google.firebase.sessions.testing.FakeFirebaseApp
24+
import org.junit.After
2025
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.robolectric.RobolectricTestRunner
2128

29+
@RunWith(RobolectricTestRunner::class)
2230
class SessionEventTest {
2331
@Test
2432
fun sessionStart_populatesSessionDetailsCorrectly() {
@@ -29,8 +37,7 @@ class SessionEventTest {
2937
collectEvents = true,
3038
sessionIndex = 3,
3139
)
32-
33-
val sessionEvent = SessionEvents.startSession(sessionDetails)
40+
val sessionEvent = SessionEvents.startSession(FakeFirebaseApp.fakeFirebaseApp(), sessionDetails)
3441

3542
assertThat(sessionEvent)
3643
.isEqualTo(
@@ -41,8 +48,24 @@ class SessionEventTest {
4148
sessionId = "a1b2c3",
4249
firstSessionId = "a1a1a1",
4350
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+
),
4462
)
4563
)
4664
)
4765
}
66+
67+
@After
68+
fun cleanUp() {
69+
FirebaseApp.clearInstancesForTest()
70+
}
4871
}
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.testing
18+
19+
import android.content.Context
20+
import androidx.test.core.app.ApplicationProvider
21+
import androidx.test.core.content.pm.PackageInfoBuilder
22+
import com.google.firebase.FirebaseApp
23+
import com.google.firebase.FirebaseOptions
24+
import com.google.firebase.ktx.Firebase
25+
import com.google.firebase.ktx.initialize
26+
import org.robolectric.Shadows
27+
28+
object FakeFirebaseApp {
29+
internal val MOCK_PROJECT_ID = "project"
30+
internal val MOCK_APP_ID = "1:12345:android:app"
31+
internal val MOCK_API_KEY = "RANDOM_APIKEY_FOR_TESTING"
32+
internal val MOCK_APP_VERSION = "1.0.0"
33+
34+
fun fakeFirebaseApp(): FirebaseApp {
35+
val shadowPackageManager =
36+
Shadows.shadowOf(ApplicationProvider.getApplicationContext<Context>().packageManager)
37+
val packageInfo =
38+
PackageInfoBuilder.newBuilder()
39+
.setPackageName(ApplicationProvider.getApplicationContext<Context>().packageName)
40+
.build()
41+
packageInfo.versionName = MOCK_APP_VERSION
42+
shadowPackageManager.installPackage(packageInfo)
43+
44+
val firebaseApp =
45+
Firebase.initialize(
46+
ApplicationProvider.getApplicationContext(),
47+
FirebaseOptions.Builder()
48+
.setApplicationId(MOCK_APP_ID)
49+
.setApiKey(MOCK_API_KEY)
50+
.setProjectId(MOCK_PROJECT_ID)
51+
.build()
52+
)
53+
return firebaseApp
54+
}
55+
}

0 commit comments

Comments
 (0)