Skip to content

Commit a3136c9

Browse files
authored
Implement AppCheck Android token-changed listeners (#1229)
1 parent e774aff commit a3136c9

File tree

4 files changed

+145
-10
lines changed

4 files changed

+145
-10
lines changed

app_check/integration_test/src/integration_test.cc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,6 @@ TEST_F(FirebaseAppCheckTest, TestGetTokenLastResult) {
501501
future2.result()->expire_time_millis);
502502
}
503503

504-
// Android does not yet implement token changed listeners
505-
#if !FIREBASE_PLATFORM_ANDROID
506504
TEST_F(FirebaseAppCheckTest, TestAddTokenChangedListener) {
507505
InitializeAppCheckWithDebug();
508506
InitializeApp();
@@ -522,10 +520,7 @@ TEST_F(FirebaseAppCheckTest, TestAddTokenChangedListener) {
522520
ASSERT_EQ(token_changed_listener.num_token_changes_, 1);
523521
EXPECT_EQ(token_changed_listener.last_token_.token, token.token);
524522
}
525-
#endif // !FIREBASE_PLATFORM_ANDROID
526523

527-
// Android does not yet implement token changed listeners
528-
#if !FIREBASE_PLATFORM_ANDROID
529524
TEST_F(FirebaseAppCheckTest, TestRemoveTokenChangedListener) {
530525
InitializeAppCheckWithDebug();
531526
InitializeApp();
@@ -544,7 +539,6 @@ TEST_F(FirebaseAppCheckTest, TestRemoveTokenChangedListener) {
544539

545540
ASSERT_EQ(token_changed_listener.num_token_changes_, 0);
546541
}
547-
#endif // !FIREBASE_PLATFORM_ANDROID
548542

549543
TEST_F(FirebaseAppCheckTest, TestSignIn) {
550544
InitializeAppCheckWithDebug();

app_check/src/android/app_check_android.cc

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "app/src/embedded_file.h"
2121
#include "app/src/include/firebase/future.h"
22+
#include "app/src/include/firebase/internal/mutex.h"
2223
#include "app/src/reference_counted_future_impl.h"
2324
#include "app/src/util_android.h"
2425
#include "app_check/app_check_resources.h"
@@ -100,6 +101,27 @@ static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = {
100101
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)},
101102
};
102103

104+
// clang-format off
105+
#define JNI_APP_CHECK_LISTENER_METHODS(X) \
106+
X(Constructor, "<init>", "(J)V")
107+
// clang-format on
108+
109+
METHOD_LOOKUP_DECLARATION(jni_app_check_listener,
110+
JNI_APP_CHECK_LISTENER_METHODS)
111+
METHOD_LOOKUP_DEFINITION(
112+
jni_app_check_listener,
113+
"com/google/firebase/appcheck/internal/cpp/JniAppCheckListener",
114+
JNI_APP_CHECK_LISTENER_METHODS)
115+
116+
JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged(
117+
JNIEnv* env, jobject clazz, jlong c_app_check, jobject token);
118+
119+
static const JNINativeMethod kNativeJniAppCheckListenerMethods[] = {
120+
{"nativeOnAppCheckTokenChanged",
121+
"(JLcom/google/firebase/appcheck/AppCheckToken;)V",
122+
reinterpret_cast<void*>(JniAppCheckListener_nativeOnAppCheckTokenChanged)},
123+
};
124+
103125
static const char* kApiIdentifier = "AppCheck";
104126

105127
static AppCheckProviderFactory* g_provider_factory = nullptr;
@@ -127,13 +149,24 @@ bool CacheAppCheckMethodIds(
127149
FIREBASE_ARRAYSIZE(kNativeJniAppCheckProviderMethods)))) {
128150
return false;
129151
}
152+
// Cache the JniAppCheckListener class and register the native callback
153+
// methods.
154+
if (!(jni_app_check_listener::CacheClassFromFiles(env, activity,
155+
&embedded_files) &&
156+
jni_app_check_listener::CacheMethodIds(env, activity) &&
157+
jni_app_check_listener::RegisterNatives(
158+
env, kNativeJniAppCheckListenerMethods,
159+
FIREBASE_ARRAYSIZE(kNativeJniAppCheckListenerMethods)))) {
160+
return false;
161+
}
130162
return app_check::CacheMethodIds(env, activity);
131163
}
132164

133165
void ReleaseAppCheckClasses(JNIEnv* env) {
134166
app_check::ReleaseClass(env);
135167
jni_provider_factory::ReleaseClass(env);
136168
jni_provider::ReleaseClass(env);
169+
jni_app_check_listener::ReleaseClass(env);
137170
}
138171

139172
// Release cached Java classes.
@@ -195,7 +228,7 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
195228
jobject task_completion_source_global =
196229
env->NewGlobalRef(task_completion_source);
197230

198-
// Defines a c++ callback method to call
231+
// Defines a C++ callback method to call
199232
// JniAppCheckProvider.HandleGetTokenResult with the resulting token
200233
auto token_callback{[j_provider_global, task_completion_source_global](
201234
firebase::app_check::AppCheckToken token,
@@ -219,6 +252,13 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
219252
provider->GetToken(token_callback);
220253
}
221254

255+
JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged(
256+
JNIEnv* env, jobject clazz, jlong c_app_check, jobject token) {
257+
auto app_check_internal = reinterpret_cast<AppCheckInternal*>(c_app_check);
258+
AppCheckToken cpp_token = CppTokenFromAndroidToken(env, token);
259+
app_check_internal->NotifyTokenChanged(cpp_token);
260+
}
261+
222262
AppCheckInternal::AppCheckInternal(App* app) : app_(app) {
223263
future_manager().AllocFutureApi(this, kAppCheckFnCount);
224264

@@ -284,16 +324,40 @@ AppCheckInternal::AppCheckInternal(App* app) : app_(app) {
284324
FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env));
285325
env->DeleteLocalRef(j_factory);
286326
}
327+
328+
// Add a token-changed listener and give it a pointer to the C++ listeners.
329+
jobject j_listener =
330+
env->NewObject(jni_app_check_listener::GetClass(),
331+
jni_app_check_listener::GetMethodId(
332+
jni_app_check_listener::kConstructor),
333+
reinterpret_cast<jlong>(this));
334+
FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env));
335+
env->CallVoidMethod(app_check_impl_,
336+
app_check::GetMethodId(app_check::kAddAppCheckListener),
337+
j_listener);
338+
FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env));
339+
j_app_check_listener_ = env->NewGlobalRef(j_listener);
340+
env->DeleteLocalRef(j_listener);
287341
} else {
288342
app_check_impl_ = nullptr;
343+
j_app_check_listener_ = nullptr;
289344
}
290345
}
291346

292347
AppCheckInternal::~AppCheckInternal() {
293348
future_manager().ReleaseFutureApi(this);
294349
JNIEnv* env = app_->GetJNIEnv();
295350
app_ = nullptr;
351+
listeners_.clear();
296352

353+
if (j_app_check_listener_ != nullptr) {
354+
env->CallVoidMethod(
355+
app_check_impl_,
356+
app_check::GetMethodId(app_check::kRemoveAppCheckListener),
357+
j_app_check_listener_);
358+
FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env));
359+
env->DeleteGlobalRef(j_app_check_listener_);
360+
}
297361
if (app_check_impl_ != nullptr) {
298362
env->DeleteGlobalRef(app_check_impl_);
299363
}
@@ -314,7 +378,7 @@ ReferenceCountedFutureImpl* AppCheckInternal::future() {
314378

315379
void AppCheckInternal::SetAppCheckProviderFactory(
316380
AppCheckProviderFactory* factory) {
317-
// Store the c++ factory in a static variable.
381+
// Store the C++ factory in a static variable.
318382
// Whenever an instance of AppCheck is created, it will read this variable
319383
// and install the factory as it is initialized.
320384
g_provider_factory = factory;
@@ -357,9 +421,28 @@ Future<AppCheckToken> AppCheckInternal::GetAppCheckTokenLastResult() {
357421
future()->LastResult(kAppCheckFnGetAppCheckToken));
358422
}
359423

360-
void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {}
424+
void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {
425+
MutexLock lock(listeners_mutex_);
426+
auto it = std::find(listeners_.begin(), listeners_.end(), listener);
427+
if (it == listeners_.end()) {
428+
listeners_.push_back(listener);
429+
}
430+
}
431+
432+
void AppCheckInternal::RemoveAppCheckListener(AppCheckListener* listener) {
433+
MutexLock lock(listeners_mutex_);
434+
auto it = std::find(listeners_.begin(), listeners_.end(), listener);
435+
if (it != listeners_.end()) {
436+
listeners_.erase(it);
437+
}
438+
}
361439

362-
void AppCheckInternal::RemoveAppCheckListener(AppCheckListener* listener) {}
440+
void AppCheckInternal::NotifyTokenChanged(AppCheckToken token) {
441+
MutexLock lock(listeners_mutex_);
442+
for (AppCheckListener* listener : listeners_) {
443+
listener->OnAppCheckTokenChanged(token);
444+
}
445+
}
363446

364447
} // namespace internal
365448
} // namespace app_check

app_check/src/android/app_check_android.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
#ifndef FIREBASE_APP_CHECK_SRC_ANDROID_APP_CHECK_ANDROID_H_
1616
#define FIREBASE_APP_CHECK_SRC_ANDROID_APP_CHECK_ANDROID_H_
1717

18+
#include <vector>
19+
1820
#include "app/src/future_manager.h"
1921
#include "app/src/include/firebase/app.h"
2022
#include "app/src/include/firebase/future.h"
23+
#include "app/src/include/firebase/internal/mutex.h"
2124
#include "app/src/util_android.h"
2225
#include "app_check/src/include/firebase/app_check.h"
2326

@@ -45,14 +48,27 @@ class AppCheckInternal {
4548

4649
void RemoveAppCheckListener(AppCheckListener* listener);
4750

51+
void NotifyTokenChanged(AppCheckToken token);
52+
4853
FutureManager& future_manager() { return future_manager_; }
4954

5055
ReferenceCountedFutureImpl* future();
5156

5257
private:
5358
::firebase::App* app_;
59+
60+
// A Java FirebaseAppCheck instance
5461
jobject app_check_impl_;
5562

63+
// A Java AppCheckListener instance
64+
jobject j_app_check_listener_;
65+
66+
// A collection of C++ AppCheckListeners
67+
std::vector<AppCheckListener*> listeners_;
68+
69+
// Lock object for accessing listeners_.
70+
Mutex listeners_mutex_;
71+
5672
FutureManager future_manager_;
5773
};
5874

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.appcheck.internal.cpp;
18+
19+
import androidx.annotation.NonNull;
20+
import com.google.firebase.appcheck.AppCheckToken;
21+
import com.google.firebase.appcheck.FirebaseAppCheck.AppCheckListener;
22+
23+
/**
24+
* An AppCheckListener that notifies C++ of token changes.
25+
*/
26+
public class JniAppCheckListener implements AppCheckListener {
27+
// A C++ pointer to AppCheckInternal
28+
private long cAppCheck;
29+
30+
JniAppCheckListener(long cAppCheck) {
31+
this.cAppCheck = cAppCheck;
32+
}
33+
34+
public void onAppCheckTokenChanged(@NonNull AppCheckToken token) {
35+
nativeOnAppCheckTokenChanged(cAppCheck, token);
36+
}
37+
38+
/**
39+
* This function is implemented in the AppCheck C++ library (app_check_android.cc).
40+
*/
41+
private native void nativeOnAppCheckTokenChanged(long cAppCheck, AppCheckToken token);
42+
}

0 commit comments

Comments
 (0)