Skip to content

Package session start event's application information. #4829

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 6 commits into from
Mar 31, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.firebase.FirebaseApp
import com.google.firebase.encoders.json.NumberedEnum

/** Enum denoting different development environments. */
internal enum class LogEnvironment(override val number: Int) : NumberedEnum {
/** Unknown environment. */
LOG_ENVIRONMENT_UNKNOWN(0),

/** Autopush environment. */
LOG_ENVIRONMENT_AUTOPUSH(1),

/** Staging environment. */
LOG_ENVIRONMENT_STAGING(2),

/** Production environment. */
LOG_ENVIRONMENT_PROD(3),
}

internal data class AndroidApplicationInfo(
/** The package name of the application/bundle name. */
val packageName: String,

/** The version of the application. */
val versionName: String,
)

internal data class ApplicationInfo(
/** The Firebase Application ID of the application. */
val appId: String,

/** The model of the device that runs the application. */
val deviceModel: String,

/** The SDK version of the sessions library. */
val sessionSdkVersion: String,

/** The logging environment for the events. */
val logEnvironment: LogEnvironment,

/** The android application information for the app. */
val androidAppInfo: AndroidApplicationInfo,
)

internal fun getApplicationInfo(firebaseApp: FirebaseApp): ApplicationInfo {
val packageName = firebaseApp.applicationContext.packageName
val packageInfo = firebaseApp.applicationContext.packageManager.getPackageInfo(packageName, 0)

return ApplicationInfo(
appId = firebaseApp.options.applicationId,
deviceModel = "",
sessionSdkVersion = BuildConfig.VERSION_NAME,
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
androidAppInfo =
AndroidApplicationInfo(packageName = packageName, versionName = packageInfo.versionName)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import kotlinx.coroutines.CoroutineDispatcher

class FirebaseSessions
internal constructor(
firebaseApp: FirebaseApp,
private val firebaseApp: FirebaseApp,
firebaseInstallations: FirebaseInstallationsApi,
backgroundDispatcher: CoroutineDispatcher
) {
Expand All @@ -36,11 +36,14 @@ internal constructor(

init {
val sessionInitiator = SessionInitiator(WallClock::elapsedRealtime, this::initiateSessionStart)
val context = firebaseApp.applicationContext.applicationContext
if (context is Application) {
context.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)
val appContext = firebaseApp.applicationContext.applicationContext
if (appContext is Application) {
appContext.registerActivityLifecycleCallbacks(sessionInitiator.activityLifecycleCallbacks)
} else {
Log.w(TAG, "Failed to register lifecycle callbacks, unexpected context ${context.javaClass}.")
Log.w(
TAG,
"Failed to register lifecycle callbacks, unexpected context ${appContext.javaClass}."
)
}
}

Expand All @@ -49,7 +52,7 @@ internal constructor(

private fun initiateSessionStart() {
val sessionDetails = sessionGenerator.generateNewSession()
val sessionEvent = SessionEvents.startSession(sessionDetails)
val sessionEvent = SessionEvents.startSession(firebaseApp, sessionDetails)

if (sessionDetails.collectEvents) {
sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal data class SessionEvent(

/** Information about the session triggering the event. */
val sessionData: SessionInfo,

/** Information about the application that is generating the session events. */
val applicationInfo: ApplicationInfo,
)

/** Enum denoting all possible session event types. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.firebase.sessions

import com.google.firebase.FirebaseApp
import com.google.firebase.encoders.DataEncoder
import com.google.firebase.encoders.FieldDescriptor
import com.google.firebase.encoders.ObjectEncoderContext
Expand Down Expand Up @@ -62,7 +63,7 @@ internal object SessionEvents {
*
* Some mutable fields, e.g. firebaseInstallationId, get populated later.
*/
fun startSession(sessionDetails: SessionDetails) =
fun startSession(firebaseApp: FirebaseApp, sessionDetails: SessionDetails) =
SessionEvent(
eventType = EventType.SESSION_START,
sessionData =
Expand All @@ -71,5 +72,6 @@ internal object SessionEvents {
sessionDetails.firstSessionId,
sessionDetails.sessionIndex,
),
applicationInfo = getApplicationInfo(firebaseApp)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 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 org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class ApplicationInfoTest {

@Test
fun applicationInfo_populatesInfoCorrectly() {
val applicationInfo = getApplicationInfo(FakeFirebaseApp.fakeFirebaseApp())
assertThat(applicationInfo)
.isEqualTo(
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
)
)
)
}

@After
fun cleanUp() {
FirebaseApp.clearInstancesForTest()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class SessionCoordinatorTest {
firstSessionId = "first",
sessionIndex = 3,
),
applicationInfo =
ApplicationInfo(
appId = "",
deviceModel = "",
sessionSdkVersion = "",
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
androidAppInfo = AndroidApplicationInfo(packageName = "", versionName = "")
)
)

sessionCoordinator.attemptLoggingSessionEvent(sessionEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class SessionEventEncoderTest {
sessionIndex = 9,
firebaseInstallationId = "fid"
),
applicationInfo =
ApplicationInfo(
appId = "",
deviceModel = "",
sessionSdkVersion = "",
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
AndroidApplicationInfo(packageName = "", versionName = ""),
)
)

val json = SESSION_EVENT_ENCODER.encode(sessionEvent)
Expand Down Expand Up @@ -69,6 +77,14 @@ class SessionEventEncoderTest {
firstSessionId = "",
sessionIndex = 0,
),
applicationInfo =
ApplicationInfo(
appId = "",
deviceModel = "",
sessionSdkVersion = "",
logEnvironment = LogEnvironment.LOG_ENVIRONMENT_PROD,
AndroidApplicationInfo(packageName = "", versionName = ""),
)
)

val json = SESSION_EVENT_ENCODER.encode(sessionEvent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@

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 org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class SessionEventTest {
@Test
fun sessionStart_populatesSessionDetailsCorrectly() {
Expand All @@ -29,8 +37,7 @@ class SessionEventTest {
collectEvents = true,
sessionIndex = 3,
)

val sessionEvent = SessionEvents.startSession(sessionDetails)
val sessionEvent = SessionEvents.startSession(FakeFirebaseApp.fakeFirebaseApp(), sessionDetails)

assertThat(sessionEvent)
.isEqualTo(
Expand All @@ -41,8 +48,24 @@ class SessionEventTest {
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
),
)
)
)
}

@After
fun cleanUp() {
FirebaseApp.clearInstancesForTest()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.core.content.pm.PackageInfoBuilder
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.initialize
import org.robolectric.Shadows

object FakeFirebaseApp {
internal val MOCK_PROJECT_ID = "project"
internal val MOCK_APP_ID = "1:12345:android:app"
internal val MOCK_API_KEY = "RANDOM_APIKEY_FOR_TESTING"
internal val MOCK_APP_VERSION = "1.0.0"

fun fakeFirebaseApp(): FirebaseApp {
val shadowPackageManager =
Shadows.shadowOf(ApplicationProvider.getApplicationContext<Context>().packageManager)
val packageInfo =
PackageInfoBuilder.newBuilder()
.setPackageName(ApplicationProvider.getApplicationContext<Context>().packageName)
.build()
packageInfo.versionName = MOCK_APP_VERSION
shadowPackageManager.installPackage(packageInfo)

val firebaseApp =
Firebase.initialize(
ApplicationProvider.getApplicationContext(),
FirebaseOptions.Builder()
.setApplicationId(MOCK_APP_ID)
.setApiKey(MOCK_API_KEY)
.setProjectId(MOCK_PROJECT_ID)
.build()
)
return firebaseApp
}
}