Skip to content

Commit 00f25ac

Browse files
authored
[Sessions] Add ApplicationInfo for filling in the proto (#10376)
1 parent 646e6a9 commit 00f25ac

9 files changed

+263
-13
lines changed

FirebaseSessions.podspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@ Pod::Spec.new do |s|
3939
base_dir + 'Protogen/**/*.{c,h,m,mm}',
4040
]
4141

42+
s.ios.framework = 'CoreTelephony'
4243
s.dependency 'FirebaseCore', '~> 10.0'
4344
s.dependency 'FirebaseCoreExtension', '~> 10.0'
4445
s.dependency 'FirebaseInstallations', '~> 10.0'
4546
s.dependency 'GoogleDataTransport', '~> 9.2'
47+
s.dependency 'GoogleUtilities/Environment', '~> 7.8'
4648
s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0'
4749

4850
s.pod_target_xcconfig = {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//
2+
// Copyright 2022 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+
import Foundation
17+
18+
@_implementationOnly import FirebaseCore
19+
@_implementationOnly import GoogleUtilities
20+
21+
protocol ApplicationInfoProtocol {
22+
/// Google App ID / GMP App ID
23+
var appID: String { get }
24+
25+
/// App's bundle ID / bundle short version
26+
var bundleID: String { get }
27+
28+
/// Version of the Firebase SDK
29+
var sdkVersion: String { get }
30+
31+
/// Crashlytics-specific device / OS filter values.
32+
var osName: String { get }
33+
34+
/// Validated Mobile Country Code and Mobile Network Code
35+
var mccMNC: String { get }
36+
}
37+
38+
class ApplicationInfo: ApplicationInfoProtocol {
39+
let appID: String
40+
41+
init(appID: String) {
42+
self.appID = appID
43+
}
44+
45+
var bundleID: String {
46+
return Bundle.main.bundleIdentifier ?? ""
47+
}
48+
49+
var sdkVersion: String {
50+
return FirebaseVersion()
51+
}
52+
53+
var osName: String {
54+
// TODO: Update once https://github.com/google/GoogleUtilities/pull/89 is released
55+
// to production, update this to GULAppEnvironmentUtil.appleDevicePlatform() and update
56+
// the podfile to depend on the newest version of GoogleUtilities
57+
return GULAppEnvironmentUtil.applePlatform()
58+
}
59+
60+
var mccMNC: String {
61+
return FIRSESGetMccMnc() ?? ""
62+
}
63+
}

FirebaseSessions/Sources/FirebaseSessions.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ protocol SessionsProvider {
3939
private let coordinator: SessionCoordinator
4040
private let initiator: SessionInitiator
4141
private let identifiers: Identifiers
42+
private let appInfo: ApplicationInfo
4243

4344
// MARK: - Initializers
4445

@@ -55,27 +56,30 @@ protocol SessionsProvider {
5556
let identifiers = Identifiers(installations: installations)
5657
let coordinator = SessionCoordinator(identifiers: identifiers, fireLogger: fireLogger)
5758
let initiator = SessionInitiator()
59+
let appInfo = ApplicationInfo(appID: appID)
5860

5961
self.init(appID: appID,
6062
identifiers: identifiers,
6163
coordinator: coordinator,
62-
initiator: initiator)
64+
initiator: initiator,
65+
appInfo: appInfo)
6366
}
6467

6568
// Initializes the SDK and begines the process of listening for lifecycle events and logging events
6669
init(appID: String, identifiers: Identifiers, coordinator: SessionCoordinator,
67-
initiator: SessionInitiator) {
70+
initiator: SessionInitiator, appInfo: ApplicationInfo) {
6871
self.appID = appID
6972

7073
self.identifiers = identifiers
7174
self.coordinator = coordinator
7275
self.initiator = initiator
76+
self.appInfo = appInfo
7377

7478
super.init()
7579

7680
self.initiator.beginListening {
7781
self.identifiers.generateNewSessionID()
78-
let event = SessionStartEvent(identifiers: self.identifiers)
82+
let event = SessionStartEvent(identifiers: self.identifiers, appInfo: self.appInfo)
7983
DispatchQueue.global().async {
8084
self.coordinator.attemptLoggingSessionStart(event: event) { result in
8185
}

FirebaseSessions/Sources/NanoPB/FIRSESNanoPBHelpers.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
#ifndef FIRSESNanoPBHelpers_h
1717
#define FIRSESNanoPBHelpers_h
1818

19+
#import <TargetConditionals.h>
20+
#if __has_include("CoreTelephony/CTTelephonyNetworkInfo.h") && !TARGET_OS_MACCATALYST && \
21+
!TARGET_OS_OSX && !TARGET_OS_TV
22+
#define TARGET_HAS_MOBILE_CONNECTIVITY
23+
#import <CoreTelephony/CTCarrier.h>
24+
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
25+
#endif
26+
1927
#import <nanopb/pb.h>
2028
#import <nanopb/pb_decode.h>
2129
#import <nanopb/pb_encode.h>
@@ -57,6 +65,9 @@ BOOL FIRSESIsPBStringEqual(pb_bytes_array_t* _Nullable pbString, NSString* _Null
5765
/// @param data NSData that's expected
5866
BOOL FIRSESIsPBDataEqual(pb_bytes_array_t* _Nullable pbArray, NSData* _Nullable data);
5967

68+
/// Returns the validated MccMnc if it is available, or nil if the device does not support telephone
69+
NSString* _Nullable FIRSESGetMccMnc(void);
70+
6071
NS_ASSUME_NONNULL_END
6172

6273
#endif /* FIRSESNanoPBHelpers_h */

FirebaseSessions/Sources/NanoPB/FIRSESNanoPBHelpers.m

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
#import <Foundation/Foundation.h>
1717

18-
#import "FirebaseSessions/Sources/NanoPB/FIRSESNanoPBHelpers.m"
18+
#import "FirebaseSessions/Sources/NanoPB/FIRSESNanoPBHelpers.h"
1919

2020
#import <nanopb/pb.h>
2121
#import <nanopb/pb_decode.h>
@@ -126,4 +126,37 @@ BOOL FIRSESIsPBDataEqual(pb_bytes_array_t *_Nullable pbArray, NSData *_Nullable
126126
return equal;
127127
}
128128

129+
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
130+
CTTelephonyNetworkInfo *_Nullable FIRSESNetworkInfo(void) {
131+
static CTTelephonyNetworkInfo *networkInfo;
132+
static dispatch_once_t onceToken;
133+
dispatch_once(&onceToken, ^{
134+
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
135+
});
136+
return networkInfo;
137+
}
138+
139+
NSString *FIRSESValidatedMccMnc(NSString *mcc, NSString *mnc) {
140+
if ([mcc length] != 3 || [mnc length] < 2 || [mnc length] > 3) return nil;
141+
142+
static NSCharacterSet *notDigits;
143+
static dispatch_once_t token;
144+
dispatch_once(&token, ^{
145+
notDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
146+
});
147+
NSString *mccMnc = [mcc stringByAppendingString:mnc];
148+
if ([mccMnc rangeOfCharacterFromSet:notDigits].location != NSNotFound) return nil;
149+
return mccMnc;
150+
}
151+
#endif
152+
153+
NSString *_Nullable FIRSESGetMccMnc(void) {
154+
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
155+
CTTelephonyNetworkInfo *networkInfo = FIRSESNetworkInfo();
156+
CTCarrier *provider = networkInfo.subscriberCellularProvider;
157+
return FIRSESValidatedMccMnc(provider.mobileCountryCode, provider.mobileNetworkCode);
158+
#endif
159+
return nil;
160+
}
161+
129162
NS_ASSUME_NONNULL_END

FirebaseSessions/Sources/SessionStartEvent.swift

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import Foundation
2525
class SessionStartEvent: NSObject, GDTCOREventDataObject {
2626
var proto: firebase_appquality_sessions_SessionEvent
2727

28-
init(identifiers: IdentifierProvider, time: TimeProvider = Time()) {
28+
init(identifiers: IdentifierProvider, appInfo: ApplicationInfoProtocol,
29+
time: TimeProvider = Time()) {
2930
proto = firebase_appquality_sessions_SessionEvent()
3031

3132
super.init()
@@ -34,16 +35,23 @@ class SessionStartEvent: NSObject, GDTCOREventDataObject {
3435
proto.session_data.session_id = makeProtoString(identifiers.sessionID)
3536
proto.session_data.previous_session_id = makeProtoString(identifiers.previousSessionID)
3637
proto.session_data.event_timestamp_us = time.timestampUS
38+
39+
proto.application_info.app_id = makeProtoString(appInfo.appID)
40+
proto.application_info.session_sdk_version = makeProtoString(appInfo.sdkVersion)
41+
// proto.application_info.device_model = makeProtoString(appInfo.deviceModel)
42+
// proto.application_info.development_platform_name;
43+
// proto.application_info.development_platform_version;
44+
45+
proto.application_info.apple_app_info.bundle_short_version = makeProtoString(appInfo.bundleID)
46+
// proto.application_info.apple_app_info.network_connection_info
47+
proto.application_info.apple_app_info.os_name = convertOSName(osName: appInfo.osName)
48+
proto.application_info.apple_app_info.mcc_mnc = makeProtoString(appInfo.mccMNC)
3749
}
3850

3951
func setInstallationID(identifiers: IdentifierProvider) {
4052
proto.session_data.firebase_installation_id = makeProtoString(identifiers.installationID)
4153
}
4254

43-
private func makeProtoString(_ string: String) -> UnsafeMutablePointer<pb_bytes_array_t>? {
44-
return FIRSESEncodeString(string)
45-
}
46-
4755
// MARK: - GDTCOREventDataObject
4856

4957
func transportBytes() -> Data {
@@ -59,4 +67,32 @@ class SessionStartEvent: NSObject, GDTCOREventDataObject {
5967
}
6068
return data
6169
}
70+
71+
// MARK: - Data Conversion
72+
73+
private func makeProtoString(_ string: String) -> UnsafeMutablePointer<pb_bytes_array_t>? {
74+
return FIRSESEncodeString(string)
75+
}
76+
77+
private func convertOSName(osName: String) -> firebase_appquality_sessions_OsName {
78+
switch osName.lowercased() {
79+
case "macos":
80+
return firebase_appquality_sessions_OsName_MACOS
81+
case "maccatalyst":
82+
return firebase_appquality_sessions_OsName_MACCATALYST
83+
case "ios_on_mac":
84+
return firebase_appquality_sessions_OsName_IOS_ON_MAC
85+
case "ios":
86+
return firebase_appquality_sessions_OsName_IOS
87+
case "tvos":
88+
return firebase_appquality_sessions_OsName_TVOS
89+
case "watchos":
90+
return firebase_appquality_sessions_OsName_WATCHOS
91+
case "ipados":
92+
return firebase_appquality_sessions_OsName_IPADOS
93+
default:
94+
Logger.logWarning("Found unknown OSName: \"\(osName)\" while converting.")
95+
return firebase_appquality_sessions_OsName_UNKNOWN_OSNAME
96+
}
97+
}
6298
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Copyright 2022 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+
import Foundation
17+
18+
@testable import FirebaseSessions
19+
20+
class MockApplicationInfo: ApplicationInfoProtocol {
21+
var appID: String = ""
22+
23+
var bundleID: String = ""
24+
25+
var sdkVersion: String = ""
26+
27+
var osName: String = ""
28+
29+
var mccMNC: String = ""
30+
31+
static let testAppID = "testAppID"
32+
static let testBundleID = "testBundleID"
33+
static let testSDKVersion = "testSDKVersion"
34+
static let testOSName = "ios"
35+
static let testMCCMNC = "testMCCMNC"
36+
37+
func mockAllInfo() {
38+
appID = MockApplicationInfo.testAppID
39+
bundleID = MockApplicationInfo.testBundleID
40+
sdkVersion = MockApplicationInfo.testSDKVersion
41+
osName = MockApplicationInfo.testOSName
42+
mccMNC = MockApplicationInfo.testMCCMNC
43+
}
44+
}

FirebaseSessions/Tests/Unit/SessionCoordinatorTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class SessionCoordinatorTests: XCTestCase {
2121
var identifiers = MockIdentifierProvider()
2222
var time = MockTimeProvider()
2323
var fireLogger = MockGDTLogger()
24+
var appInfo = MockApplicationInfo()
2425

2526
var coordinator: SessionCoordinator!
2627

@@ -33,7 +34,7 @@ class SessionCoordinatorTests: XCTestCase {
3334
func test_attemptLoggingSessionStart_logsToGDT() throws {
3435
identifiers.mockAllValidIDs()
3536

36-
let event = SessionStartEvent(identifiers: identifiers, time: time)
37+
let event = SessionStartEvent(identifiers: identifiers, appInfo: appInfo, time: time)
3738
var resultSuccess = false
3839
coordinator.attemptLoggingSessionStart(event: event) { result in
3940
switch result {
@@ -59,7 +60,7 @@ class SessionCoordinatorTests: XCTestCase {
5960
identifiers.mockAllValidIDs()
6061
fireLogger.result = .failure(NSError(domain: "TestError", code: -1))
6162

62-
let event = SessionStartEvent(identifiers: identifiers, time: time)
63+
let event = SessionStartEvent(identifiers: identifiers, appInfo: appInfo, time: time)
6364

6465
// Start success so it must be set to false
6566
var resultSuccess = true

0 commit comments

Comments
 (0)