Skip to content

Commit ef2a71f

Browse files
authored
Add GetSessionId implementation for Android, iOS, and stub. (#1135)
* Add GetSessionId implementation for Android, iOS, and stub. * Format code. * Update header doc * Fix JNI call. * Fix Android build. * Fix JNI call and Future completion call. * Use existing utility function. * Force long parameter. * Explicitly use an int64_t result. * Fix iOS build error. * Add default error code of -1 to GetSessionId error. * Fix type mismatch * Add mutex to completion handler on iOS. * Add retry when running GetSessionId on simulator. * Clarify comment. * Fix mutex * Fix build error. * Format code. * Roll back changes to info.plist files. Revert "Fix build error." This reverts commit 2a4e57f. * Add static cast to int64 type * Add readme note. * Use long long in SWIG mode, rather than int64_t. * Revert "Use long long in SWIG mode, rather than int64_t." This reverts commit 8f096b2. * Add debug output to analytics test. * Also output "Unknown error" if the status message is blank. * Add more error logging. * Fix error handling to respect exception result type. * Change flaky fix * Add more debugging to Android version. * Format code. * Make sure the consent test isn't screwing up the session ID test. * Try moving consent stuff earlier, with delays, to make sure it doesn't mess up GetSessionId. * Expanded the error message. * Fix error message. * Add logging to SetConsent on Android. * Format code. * Add skip for order-dependent test that fails, to avoid a flake. * Fix did_test_setconsent flag * Added log message for skipping test
1 parent 45f8e32 commit ef2a71f

File tree

7 files changed

+218
-21
lines changed

7 files changed

+218
-21
lines changed

analytics/integration_test/src/integration_test.cc

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
namespace firebase_testapp_automated {
4545

46+
using app_framework::LogInfo;
4647
using app_framework::ProcessEvents;
4748
using firebase_test_framework::FirebaseTest;
4849

@@ -52,9 +53,11 @@ class FirebaseAnalyticsTest : public FirebaseTest {
5253
static void TearDownTestSuite();
5354

5455
static firebase::App* shared_app_;
56+
static bool did_test_setconsent_;
5557
};
5658

57-
firebase::App* FirebaseAnalyticsTest::shared_app_;
59+
firebase::App* FirebaseAnalyticsTest::shared_app_ = nullptr;
60+
bool FirebaseAnalyticsTest::did_test_setconsent_ = false;
5861

5962
void FirebaseAnalyticsTest::SetUpTestSuite() {
6063
#if defined(__ANDROID__)
@@ -65,6 +68,7 @@ void FirebaseAnalyticsTest::SetUpTestSuite() {
6568
#endif // defined(__ANDROID__)
6669

6770
firebase::analytics::Initialize(*shared_app_);
71+
did_test_setconsent_ = false;
6872
}
6973

7074
void FirebaseAnalyticsTest::TearDownTestSuite() {
@@ -89,6 +93,53 @@ TEST_F(FirebaseAnalyticsTest, TestSetCollectionEnabled) {
8993
firebase::analytics::SetAnalyticsCollectionEnabled(true);
9094
}
9195

96+
TEST_F(FirebaseAnalyticsTest, TestSetSessionTimeoutDuraction) {
97+
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 5);
98+
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 15);
99+
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 30);
100+
}
101+
102+
TEST_F(FirebaseAnalyticsTest, TestGetAnalyticsInstanceID) {
103+
firebase::Future<std::string> future =
104+
firebase::analytics::GetAnalyticsInstanceId();
105+
WaitForCompletion(future, "GetAnalyticsInstanceId");
106+
EXPECT_FALSE(future.result()->empty());
107+
}
108+
109+
TEST_F(FirebaseAnalyticsTest, TestGetSessionID) {
110+
// On Android, if SetConsent was tested, this test will fail, since the app
111+
// needs to be restarted after consent is denied or it won't generate a new
112+
// sessionID. To not break the tests, skip this test in that case.
113+
#if defined(__ANDROID__)
114+
if (did_test_setconsent_) {
115+
LogInfo(
116+
"Skipping TestGetSessionID after TestSetConsent, as it will fail until "
117+
"the app is restarted.");
118+
GTEST_SKIP();
119+
return;
120+
}
121+
#endif
122+
firebase::Future<int64_t> future;
123+
124+
// It can take Analytics a moment to initialize on iOS.
125+
// So on iOS/tvOS, retry this test if GetSessionId returns an
126+
// error.
127+
#if TARGET_OS_IPHONE
128+
FLAKY_TEST_SECTION_BEGIN();
129+
#endif // TARGET_OS_IPHONE
130+
131+
future = firebase::analytics::GetSessionId();
132+
WaitForCompletion(future, "GetSessionId");
133+
134+
#if TARGET_OS_IPHONE
135+
FLAKY_TEST_SECTION_END();
136+
#endif // TARGET_OS_IPHONE
137+
138+
EXPECT_TRUE(future.result() != nullptr);
139+
EXPECT_NE(*future.result(), static_cast<int64_t>(0L));
140+
LogInfo("Got session ID: %" PRId64, *future.result());
141+
}
142+
92143
TEST_F(FirebaseAnalyticsTest, TestSetConsent) {
93144
// Can't confirm that these do anything but just run them all to ensure the
94145
// app doesn't crash.
@@ -107,23 +158,13 @@ TEST_F(FirebaseAnalyticsTest, TestSetConsent) {
107158
std::map<firebase::analytics::ConsentType, firebase::analytics::ConsentStatus>
108159
consent_settings_empty;
109160
firebase::analytics::SetConsent(consent_settings_empty);
161+
ProcessEvents(1000);
110162
firebase::analytics::SetConsent(consent_settings_deny);
111-
firebase::analytics::SetConsent(consent_settings_empty);
163+
ProcessEvents(1000);
112164
firebase::analytics::SetConsent(consent_settings_allow);
113-
firebase::analytics::SetConsent(consent_settings_empty);
114-
}
115-
116-
TEST_F(FirebaseAnalyticsTest, TestSetSessionTimeoutDuraction) {
117-
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 5);
118-
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 15);
119-
firebase::analytics::SetSessionTimeoutDuration(1000 * 60 * 30);
120-
}
165+
ProcessEvents(1000);
121166

122-
TEST_F(FirebaseAnalyticsTest, TestGetAnalyticsInstanceID) {
123-
firebase::Future<std::string> future =
124-
firebase::analytics::GetAnalyticsInstanceId();
125-
WaitForCompletion(future, "GetAnalyticsInstanceId");
126-
EXPECT_FALSE(future.result()->empty());
167+
did_test_setconsent_ = true;
127168
}
128169

129170
TEST_F(FirebaseAnalyticsTest, TestSetProperties) {

analytics/src/analytics_android.cc

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ static const ::firebase::App* g_app = nullptr;
5656
X(ResetAnalyticsData, "resetAnalyticsData", "()V"), \
5757
X(GetAppInstanceId, "getAppInstanceId", \
5858
"()Lcom/google/android/gms/tasks/Task;"), \
59+
X(GetSessionId, "getSessionId", \
60+
"()Lcom/google/android/gms/tasks/Task;"), \
5961
X(GetInstance, "getInstance", "(Landroid/content/Context;)" \
6062
"Lcom/google/firebase/analytics/FirebaseAnalytics;", \
6163
firebase::util::kMethodTypeStatic)
@@ -203,15 +205,23 @@ void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) {
203205
env->GetStaticObjectField(analytics_consent_type::GetClass(),
204206
analytics_consent_type::GetFieldId(
205207
analytics_consent_type::kAdStorage));
206-
util::CheckAndClearJniExceptions(env);
208+
if (util::LogException(env, kLogLevelError,
209+
"Failed to get ConsentTypeAdStorage")) {
210+
env->DeleteLocalRef(consent_map);
211+
return;
212+
}
207213
break;
208214
case kConsentTypeAnalyticsStorage:
209215
consent_type = env->GetStaticObjectField(
210216
analytics_consent_type::GetClass(),
211217
analytics_consent_type::GetFieldId(
212218
analytics_consent_type::kAnalyticsStorage));
213219

214-
util::CheckAndClearJniExceptions(env);
220+
if (util::LogException(env, kLogLevelError,
221+
"Failed to get ConsentTypeAnalyticsStorage")) {
222+
env->DeleteLocalRef(consent_map);
223+
return;
224+
}
215225
break;
216226
default:
217227
LogError("Unknown ConsentType value: %d", it->first);
@@ -225,21 +235,32 @@ void SetConsent(const std::map<ConsentType, ConsentStatus>& consent_settings) {
225235
env->GetStaticObjectField(analytics_consent_status::GetClass(),
226236
analytics_consent_status::GetFieldId(
227237
analytics_consent_status::kGranted));
228-
util::CheckAndClearJniExceptions(env);
238+
if (util::LogException(env, kLogLevelError,
239+
"Failed to get ConsentStatusGranted")) {
240+
env->DeleteLocalRef(consent_map);
241+
env->DeleteLocalRef(consent_type);
242+
return;
243+
}
229244
break;
230245
case kConsentStatusDenied:
231246
consent_status =
232247
env->GetStaticObjectField(analytics_consent_status::GetClass(),
233248
analytics_consent_status::GetFieldId(
234249
analytics_consent_status::kDenied));
235-
util::CheckAndClearJniExceptions(env);
250+
if (util::LogException(env, kLogLevelError,
251+
"Failed to get ConsentStatusDenied")) {
252+
env->DeleteLocalRef(consent_map);
253+
env->DeleteLocalRef(consent_type);
254+
return;
255+
}
236256
break;
237257
default:
238258
LogError("Unknown ConsentStatus value: %d", it->second);
239259
env->DeleteLocalRef(consent_map);
240260
env->DeleteLocalRef(consent_type);
241261
return;
242262
}
263+
LogInfo("SetConsent: %d -> %d", consent_type, consent_status);
243264
jobject previous = env->CallObjectMethod(consent_map, put_method,
244265
consent_type, consent_status);
245266
util::CheckAndClearJniExceptions(env);
@@ -495,5 +516,78 @@ Future<std::string> GetAnalyticsInstanceIdLastResult() {
495516
internal::kAnalyticsFnGetAnalyticsInstanceId));
496517
}
497518

519+
Future<int64_t> GetSessionId() {
520+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
521+
auto* api = internal::FutureData::Get()->api();
522+
const auto future_handle =
523+
api->SafeAlloc<int64_t>(internal::kAnalyticsFnGetSessionId);
524+
JNIEnv* env = g_app->GetJNIEnv();
525+
jobject task =
526+
env->CallObjectMethod(g_analytics_class_instance,
527+
analytics::GetMethodId(analytics::kGetSessionId));
528+
529+
std::string error = util::GetAndClearExceptionMessage(env);
530+
if (error.empty()) {
531+
util::RegisterCallbackOnTask(
532+
env, task,
533+
[](JNIEnv* env, jobject result, util::FutureResult result_code,
534+
const char* status_message, void* callback_data) {
535+
auto* future_data = internal::FutureData::Get();
536+
if (future_data) {
537+
FutureHandleId future_id =
538+
reinterpret_cast<FutureHandleId>(callback_data);
539+
FutureHandle handle(future_id);
540+
541+
if (result_code == util::kFutureResultSuccess) {
542+
if (result != nullptr) {
543+
// result is a Long class type, unbox it.
544+
// It'll get deleted below.
545+
uint64_t session_id = util::JLongToInt64(env, result);
546+
util::CheckAndClearJniExceptions(env);
547+
future_data->api()->CompleteWithResult(handle, 0, "",
548+
session_id);
549+
} else {
550+
// Succeeded, but with a nullptr result.
551+
// This occurs when AnalyticsStorage consent is set to Denied or
552+
// the session is expired.
553+
future_data->api()->CompleteWithResult(
554+
handle, -2,
555+
(status_message && *status_message)
556+
? status_message
557+
: "AnalyticsStorage consent is set to "
558+
"Denied, or session is expired.",
559+
0);
560+
}
561+
} else {
562+
// Failed, result is an exception, don't parse it.
563+
// It'll get deleted below.
564+
future_data->api()->CompleteWithResult(
565+
handle, -1,
566+
status_message ? status_message : "Unknown error occurred",
567+
0);
568+
LogError("getSessionId() returned an error: %s", status_message);
569+
}
570+
}
571+
if (result) env->DeleteLocalRef(result);
572+
},
573+
reinterpret_cast<void*>(future_handle.get().id()),
574+
internal::kAnalyticsModuleName);
575+
} else {
576+
LogError("GetSessionId() threw an exception: %s", error.c_str());
577+
api->CompleteWithResult(future_handle.get(), -1, error.c_str(),
578+
static_cast<int64_t>(0L));
579+
}
580+
env->DeleteLocalRef(task);
581+
582+
return Future<int64_t>(api, future_handle.get());
583+
}
584+
585+
Future<int64_t> GetSessionIdLastResult() {
586+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
587+
return static_cast<const Future<int64_t>&>(
588+
internal::FutureData::Get()->api()->LastResult(
589+
internal::kAnalyticsFnGetSessionId));
590+
}
591+
498592
} // namespace analytics
499593
} // namespace firebase

analytics/src/analytics_common.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ namespace firebase {
2323
namespace analytics {
2424
namespace internal {
2525

26-
enum AnalyticsFn { kAnalyticsFnGetAnalyticsInstanceId, kAnalyticsFnCount };
26+
enum AnalyticsFn {
27+
kAnalyticsFnGetAnalyticsInstanceId,
28+
kAnalyticsFnGetSessionId,
29+
kAnalyticsFnCount
30+
};
2731

2832
// Data structure which holds the Future API for this module.
2933
class FutureData {

analytics/src/analytics_ios.mm

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,5 +328,29 @@ Thread get_id_thread(
328328
internal::FutureData::Get()->api()->LastResult(internal::kAnalyticsFnGetAnalyticsInstanceId));
329329
}
330330

331+
Future<int64_t> GetSessionId() {
332+
MutexLock lock(g_mutex);
333+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
334+
auto* api = internal::FutureData::Get()->api();
335+
const auto future_handle = api->SafeAlloc<int64_t>(internal::kAnalyticsFnGetSessionId);
336+
[FIRAnalytics sessionIDWithCompletion:^(int64_t session_id, NSError* _Nullable error) {
337+
MutexLock lock(g_mutex);
338+
if (!internal::IsInitialized()) return;
339+
if (error) {
340+
api->Complete(future_handle, -1, util::NSStringToString(error.localizedDescription).c_str());
341+
} else {
342+
api->CompleteWithResult(future_handle, 0, "", session_id);
343+
}
344+
}];
345+
return MakeFuture<int64_t>(api, future_handle);
346+
}
347+
348+
Future<int64_t> GetSessionIdLastResult() {
349+
MutexLock lock(g_mutex);
350+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
351+
return static_cast<const Future<int64_t>&>(
352+
internal::FutureData::Get()->api()->LastResult(internal::kAnalyticsFnGetSessionId));
353+
}
354+
331355
} // namespace analytics
332356
} // namespace firebase

analytics/src/analytics_stub.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,22 @@ Future<std::string> GetAnalyticsInstanceIdLastResult() {
150150
internal::kAnalyticsFnGetAnalyticsInstanceId));
151151
}
152152

153+
Future<int64_t> GetSessionId() {
154+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
155+
auto* api = internal::FutureData::Get()->api();
156+
const auto future_handle =
157+
api->SafeAlloc<int64_t>(internal::kAnalyticsFnGetSessionId);
158+
int64_t session_id = 0x5E5510171D570BL; // "SESSIONIDSTUB", kinda
159+
api->CompleteWithResult(future_handle, 0, "", session_id);
160+
return Future<int64_t>(api, future_handle.get());
161+
}
162+
163+
Future<int64_t> GetSessionIdLastResult() {
164+
FIREBASE_ASSERT_RETURN(Future<int64_t>(), internal::IsInitialized());
165+
return static_cast<const Future<int64_t>&>(
166+
internal::FutureData::Get()->api()->LastResult(
167+
internal::kAnalyticsFnGetSessionId));
168+
}
169+
153170
} // namespace analytics
154171
} // namespace firebase

analytics/src/include/firebase/analytics.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,22 @@ Future<std::string> GetAnalyticsInstanceId();
547547
/// @returns Object which can be used to retrieve the analytics instance ID.
548548
Future<std::string> GetAnalyticsInstanceIdLastResult();
549549

550+
/// Asynchronously retrieves the identifier of the current app
551+
/// session.
552+
///
553+
/// The session ID retrieval could fail due to Analytics collection
554+
/// disabled, or if the app session was expired.
555+
///
556+
/// @returns The identifier of the current app session. The value is 0 if the
557+
/// request failed.
558+
Future<int64_t> GetSessionId();
559+
560+
/// Get the result of the most recent GetSessionId() call.
561+
///
562+
/// @returns The identifier of the current app session. The value is 0 if the
563+
/// request failed.
564+
Future<int64_t> GetSessionIdLastResult();
565+
550566
} // namespace analytics
551567
} // namespace firebase
552568

release_build_files/readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,8 @@ code.
644644
## Release Notes
645645
### Upcoming Release
646646
- Changes
647-
- Analytics: Add `analytics::SetConsent()` API.
647+
- Analytics: Add `analytics::SetConsent()` and `analytics::GetSessionId()`
648+
APIs.
648649
- General (macOS): In order to support sandbox mode, apps can define a
649650
key/value pair for FBAppGroupEntitlementName in Info.plist. The value
650651
associated with this key will be used to prefix semaphore names

0 commit comments

Comments
 (0)