From 39cc6165f5b31493ad38a01095a9c548aef694b4 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:16:03 -0700 Subject: [PATCH 01/27] Start copying over the files. --- .github/workflows/integration_tests.yml | 4 +- Android/firebase_dependencies.gradle | 2 + CMakeLists.txt | 8 +- build_scripts/packaging.conf | 2 +- .../Android/firebase_dependencies.gradle | 2 + scripts/gha/report_build_status.py | 3 + ump/CMakeLists.txt | 118 + ump/build.gradle | 90 + ump/integration_test/AndroidManifest.xml | 53 + ump/integration_test/CMakeLists.txt | 242 ++ .../AppIcon.appiconset/Contents.json | 98 + .../LaunchImage.launchimage/Contents.json | 51 + ump/integration_test/Info.plist | 41 + ump/integration_test/LaunchScreen.storyboard | 7 + ump/integration_test/LibraryManifest.xml | 23 + ump/integration_test/Podfile | 18 + ump/integration_test/build.gradle | 102 + ump/integration_test/empty.swift | 9 + ump/integration_test/googletest.cmake | 35 + ump/integration_test/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + ump/integration_test/gradlew | 178 + ump/integration_test/gradlew.bat | 104 + .../project.pbxproj | 383 ++ ump/integration_test/proguard.pro | 2 + ump/integration_test/res/layout/main.xml | 28 + ump/integration_test/res/values/strings.xml | 20 + ump/integration_test/settings.gradle | 41 + ump/integration_test/src/integration_test.cc | 3328 +++++++++++++++++ .../ump/consent_info_internal_android.cc | 668 ++++ .../ump/consent_info_internal_android.h | 132 + ump/src/common/ump/consent_info.cc | 184 + ump/src/common/ump/consent_info_internal.cc | 90 + ump/src/common/ump/consent_info_internal.h | 142 + ump/src/include/firebase/gma.h | 243 ++ ump/src/include/firebase/gma/ad_view.h | 272 ++ .../include/firebase/gma/internal/README.md | 6 + .../include/firebase/gma/internal/native_ad.h | 177 + .../firebase/gma/internal/query_info.h | 120 + .../include/firebase/gma/interstitial_ad.h | 132 + ump/src/include/firebase/gma/rewarded_ad.h | 156 + ump/src/include/firebase/gma/types.h | 959 +++++ ump/src/include/firebase/gma/ump.h | 23 + .../include/firebase/gma/ump/consent_info.h | 249 ++ ump/src/include/firebase/gma/ump/types.h | 179 + ump/src/ios/.clang-format | 2 + ump/src/ios/ump/consent_info_internal_ios.h | 69 + ump/src/ios/ump/consent_info_internal_ios.mm | 369 ++ .../stub/ump/consent_info_internal_stub.cc | 167 + ump/src/stub/ump/consent_info_internal_stub.h | 86 + .../gma/internal/cpp/AdInspectorHelper.java | 49 + .../gma/internal/cpp/AdRequestHelper.java | 48 + .../gma/internal/cpp/AdViewHelper.java | 658 ++++ .../gma/internal/cpp/ConsentInfoHelper.java | 312 ++ .../gma/internal/cpp/ConstantsHelper.java | 105 + .../gma/internal/cpp/DownloadHelper.java | 198 + .../internal/cpp/GmaInitializationHelper.java | 37 + .../internal/cpp/InterstitialAdHelper.java | 319 ++ .../gma/internal/cpp/NativeAdHelper.java | 338 ++ .../gma/internal/cpp/QueryInfoHelper.java | 218 ++ .../gma/internal/cpp/RewardedAdHelper.java | 345 ++ ump/ump_additional.pro | 2 + ump/ump_resources/AndroidManifest.xml | 6 + ump/ump_resources/build.gradle | 70 + 65 files changed, 12126 insertions(+), 4 deletions(-) create mode 100644 ump/CMakeLists.txt create mode 100644 ump/build.gradle create mode 100644 ump/integration_test/AndroidManifest.xml create mode 100644 ump/integration_test/CMakeLists.txt create mode 100644 ump/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ump/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json create mode 100644 ump/integration_test/Info.plist create mode 100644 ump/integration_test/LaunchScreen.storyboard create mode 100644 ump/integration_test/LibraryManifest.xml create mode 100644 ump/integration_test/Podfile create mode 100644 ump/integration_test/build.gradle create mode 100644 ump/integration_test/empty.swift create mode 100644 ump/integration_test/googletest.cmake create mode 100644 ump/integration_test/gradle.properties create mode 100644 ump/integration_test/gradle/wrapper/gradle-wrapper.jar create mode 100644 ump/integration_test/gradle/wrapper/gradle-wrapper.properties create mode 100755 ump/integration_test/gradlew create mode 100644 ump/integration_test/gradlew.bat create mode 100644 ump/integration_test/integration_test.xcodeproj/project.pbxproj create mode 100644 ump/integration_test/proguard.pro create mode 100644 ump/integration_test/res/layout/main.xml create mode 100644 ump/integration_test/res/values/strings.xml create mode 100644 ump/integration_test/settings.gradle create mode 100644 ump/integration_test/src/integration_test.cc create mode 100644 ump/src/android/ump/consent_info_internal_android.cc create mode 100644 ump/src/android/ump/consent_info_internal_android.h create mode 100644 ump/src/common/ump/consent_info.cc create mode 100644 ump/src/common/ump/consent_info_internal.cc create mode 100644 ump/src/common/ump/consent_info_internal.h create mode 100644 ump/src/include/firebase/gma.h create mode 100644 ump/src/include/firebase/gma/ad_view.h create mode 100644 ump/src/include/firebase/gma/internal/README.md create mode 100644 ump/src/include/firebase/gma/internal/native_ad.h create mode 100644 ump/src/include/firebase/gma/internal/query_info.h create mode 100644 ump/src/include/firebase/gma/interstitial_ad.h create mode 100644 ump/src/include/firebase/gma/rewarded_ad.h create mode 100644 ump/src/include/firebase/gma/types.h create mode 100644 ump/src/include/firebase/gma/ump.h create mode 100644 ump/src/include/firebase/gma/ump/consent_info.h create mode 100644 ump/src/include/firebase/gma/ump/types.h create mode 100644 ump/src/ios/.clang-format create mode 100644 ump/src/ios/ump/consent_info_internal_ios.h create mode 100644 ump/src/ios/ump/consent_info_internal_ios.mm create mode 100644 ump/src/stub/ump/consent_info_internal_stub.cc create mode 100644 ump/src/stub/ump/consent_info_internal_stub.h create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java create mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java create mode 100644 ump/ump_additional.pro create mode 100644 ump/ump_resources/AndroidManifest.xml create mode 100644 ump/ump_resources/build.gradle diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index ad3a9e6a05..a600257dc8 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -17,7 +17,7 @@ on: required: true apis: description: 'CSV of apis to build and test' - default: 'analytics,app_check,auth,database,dynamic_links,firestore,functions,gma,installations,messaging,remote_config,storage' + default: 'analytics,app_check,auth,database,dynamic_links,firestore,functions,gma,installations,messaging,remote_config,storage,ump' required: true operating_systems: description: 'CSV of VMs to run on' @@ -192,7 +192,7 @@ jobs: # list. Then we can use fromJson to define the field in the matrix for the tests job. if [[ "${{ github.event.schedule }}" == "0 9 * * *" ]]; then # at 1am PST/2am PDT. Running integration tests and generate test report for all testapps except firestore - apis="analytics,app_check,auth,database,dynamic_links,functions,gma,installations,messaging,remote_config,storage" + apis="analytics,app_check,auth,database,dynamic_links,functions,gma,installations,messaging,remote_config,storage,ump" echo "::warning ::Running main nightly tests" elif [[ "${{ github.event.schedule }}" == "0 10 * * *" || "${{ github.event.schedule }}" == "0 11 * * *" ]]; then # at 2am PST/3am PDT and 3am PST/4am PDT. Running integration tests for firestore and generate test report. diff --git a/Android/firebase_dependencies.gradle b/Android/firebase_dependencies.gradle index ac7691f9a6..0394146816 100644 --- a/Android/firebase_dependencies.gradle +++ b/Android/firebase_dependencies.gradle @@ -29,6 +29,8 @@ def firebaseDependenciesMap = [ 'functions' : ['com.google.firebase:firebase-functions'], 'gma' : ['com.google.android.gms:play-services-ads:23.0.0', 'com.google.android.ump:user-messaging-platform:2.2.0'], + 'ump' : ['com.google.android.gms:play-services-ads:23.0.0', + 'com.google.android.ump:user-messaging-platform:2.2.0'], 'installations' : ['com.google.firebase:firebase-installations'], 'invites' : ['com.google.firebase:firebase-invites'], // Messaging has an additional local dependency to include. diff --git a/CMakeLists.txt b/CMakeLists.txt index 439ea7d5c7..ee714465cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,8 @@ option(FIREBASE_INCLUDE_FUNCTIONS ${FIREBASE_INCLUDE_LIBRARY_DEFAULT}) option(FIREBASE_INCLUDE_GMA "Include the GMA library." ${FIREBASE_INCLUDE_LIBRARY_DEFAULT}) +option(FIREBASE_INCLUDE_UMP "Include the UMP library." + ${FIREBASE_INCLUDE_LIBRARY_DEFAULT}) option(FIREBASE_INCLUDE_INSTALLATIONS "Include the Firebase Installations library." ${FIREBASE_INCLUDE_LIBRARY_DEFAULT}) @@ -123,9 +125,10 @@ if(FIREBASE_CPP_BUILD_TESTS OR FIREBASE_CPP_BUILD_STUB_TESTS) endif() if (PLATFORM STREQUAL TVOS OR PLATFORM STREQUAL SIMULATOR_TVOS) - # GMA and FDL are not supported on tvOS. + # GMA, UMP, and FDL are not supported on tvOS. set(FIREBASE_INCLUDE_DYNAMIC_LINKS OFF) set(FIREBASE_INCLUDE_GMA OFF) + set(FIREBASE_INCLUDE_UMP OFF) endif() # Occasionally ANDROID is not being set correctly when invoked by gradle, so @@ -630,6 +633,9 @@ endif() if (FIREBASE_INCLUDE_GMA) add_subdirectory(gma) endif() +if (FIREBASE_INCLUDE_UMP) + add_subdirectory(gma) +endif() if (FIREBASE_INCLUDE_INSTALLATIONS) add_subdirectory(installations) endif() diff --git a/build_scripts/packaging.conf b/build_scripts/packaging.conf index fd082f4b54..8f1ee6f401 100644 --- a/build_scripts/packaging.conf +++ b/build_scripts/packaging.conf @@ -3,4 +3,4 @@ # List of all Firebase products to include in the binary SDK package. readonly -a product_list=(analytics app app_check auth database dynamic_links firestore functions gma installations messaging -remote_config storage) +remote_config storage ump) diff --git a/release_build_files/Android/firebase_dependencies.gradle b/release_build_files/Android/firebase_dependencies.gradle index ede4076af2..a024285ea9 100644 --- a/release_build_files/Android/firebase_dependencies.gradle +++ b/release_build_files/Android/firebase_dependencies.gradle @@ -29,6 +29,8 @@ def firebaseDependenciesMap = [ 'functions' : ['com.google.firebase:firebase-functions'], 'gma' : ['com.google.android.gms:play-services-ads:23.0.0', 'com.google.android.ump:user-messaging-platform:2.2.0'], + 'ump' : ['com.google.android.gms:play-services-ads:23.0.0', + 'com.google.android.ump:user-messaging-platform:2.2.0'], 'installations' : ['com.google.firebase:firebase-installations'], 'invites' : ['com.google.firebase:firebase-invites'], // Messaging has an additional local dependency to include. diff --git a/scripts/gha/report_build_status.py b/scripts/gha/report_build_status.py index 38a280da46..29cf437343 100644 --- a/scripts/gha/report_build_status.py +++ b/scripts/gha/report_build_status.py @@ -193,6 +193,8 @@ def format_errors(all_errors, severity, event): product_name = 'missing logs' elif product == 'gma': product_name = product.upper() + elif product == 'ump': + product_name = product.upper() else: product_name = product.replace('_', ' ').title() @@ -664,6 +666,7 @@ def main(argv): latest = latest.replace(" ", " ") product = product.replace("_", " ") product = product.upper() if product == "gma" else product.title() + product = product.upper() if product == "ump" else product.title() if len(test_list[test_id]['links']) > 0: latest = "[%s](%s)" % (latest, test_list[test_id]['links'][-1]) diff --git a/ump/CMakeLists.txt b/ump/CMakeLists.txt new file mode 100644 index 0000000000..9365d784fd --- /dev/null +++ b/ump/CMakeLists.txt @@ -0,0 +1,118 @@ +# Copyright 2021 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. + +# CMake file for the firebase_ump library + +# Common source files used by all platforms +set(common_SRCS + src/common/ump/consent_info.cc + src/common/ump/consent_info_internal.cc + src/common/gma_common.cc +) + +# Define the resource build needed for Android +firebase_cpp_gradle(":ump:ump_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/ump_resources/build/ump_resources_lib.jar") +binary_to_array("ump_resources" + "${CMAKE_CURRENT_LIST_DIR}/ump_resources/build/ump_resources_lib.jar" + "firebase_ump" + "${FIREBASE_GEN_FILE_DIR}/ump") + +# Source files used by the Android implementation. +set(android_SRCS + ${ump_resources_source} + src/android/ump/consent_info_internal_android.cc + src/android/gma_android.cc +) + +# Source files used by the iOS implementation. +set(ios_SRCS + src/ios/ump/consent_info_internal_ios.mm + src/ios/gma_ios.mm +) + +# Source files used by the stub implementation. +set(stub_SRCS + src/stub/ump/consent_info_internal_stub.cc +) + +if(ANDROID) + set(ump_platform_SRCS + "${android_SRCS}") +elseif(IOS) + set(ump_platform_SRCS + "${ios_SRCS}") +else() + set(ump_platform_SRCS + "${stub_SRCS}") +endif() + +add_library(firebase_ump STATIC + ${common_SRCS} + ${ump_platform_SRCS}) + +set_property(TARGET firebase_ump PROPERTY FOLDER "Firebase Cpp") + +# Set up the dependency on Firebase App. +target_link_libraries(firebase_ump + PUBLIC firebase_app) +# Public headers all refer to each other relative to the src/include directory, +# while private headers are relative to the entire C++ SDK directory. +target_include_directories(firebase_ump + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/src/include + PRIVATE + ${FIREBASE_CPP_SDK_ROOT_DIR} +) +target_compile_definitions(firebase_ump + PRIVATE + -DINTERNAL_EXPERIMENTAL=1 +) +# Automatically include headers that might not be declared. +if(MSVC) + add_definitions(/FI"assert.h" /FI"string.h" /FI"stdint.h") +else() + add_definitions(-include assert.h -include string.h) +endif() + +if(ANDROID) + firebase_cpp_proguard_file(ump) +elseif(IOS) + # UMP for iOS uses weak references, which requires enabling Automatic + # Reference Counting (ARC). Also enable BitCode. + target_compile_options(firebase_ump + PUBLIC "-fobjc-arc" "-fembed-bitcode") + target_link_libraries(firebase_ump + PUBLIC "-fembed-bitcode") + + setup_pod_headers( + firebase_ump + POD_NAMES + Google-Mobile-Ads-SDK + GoogleUserMessagingPlatform + ) + + # UMP expects the header files to be in a subfolder, so set up a symlink to + # accomplish that. + symlink_pod_headers(firebase_ump GoogleUserMessagingPlatform UserMessagingPlatform) + + if (FIREBASE_XCODE_TARGET_FORMAT STREQUAL "frameworks") + set_target_properties(firebase_ump PROPERTIES + FRAMEWORK TRUE + ) + endif() +endif() + +cpp_pack_library(firebase_ump "") +cpp_pack_public_headers() diff --git a/ump/build.gradle b/ump/build.gradle new file mode 100644 index 0000000000..d2f9f50f9f --- /dev/null +++ b/ump/build.gradle @@ -0,0 +1,90 @@ +// Copyright 2021 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. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + } +} +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '32.0.0' + + sourceSets { + main { + manifest.srcFile '../android_build_files/AndroidManifest.xml' + } + } + + externalNativeBuild { + cmake { + path '../CMakeLists.txt' + } + } + + defaultConfig { + minSdkVersion 23 + targetSdkVersion 34 + versionCode 1 + versionName "1.0" + + buildTypes { + release { + minifyEnabled false + } + } + + externalNativeBuild { + cmake { + targets 'firebase_ump' + // Args are: Re-use app library prebuilt by app gradle project. + // Don't configure all the cmake subprojects. + // Only include needed project. + arguments '-DFIREBASE_CPP_USE_PRIOR_GRADLE_BUILD=ON', + '-DFIREBASE_INCLUDE_LIBRARY_DEFAULT=OFF', + '-DFIREBASE_INCLUDE_UMP=ON' + } + } + } + + lintOptions { + abortOnError false + } +} + +dependencies { + implementation project(':app') +} +apply from: "$rootDir/android_build_files/android_abis.gradle" +apply from: "$rootDir/android_build_files/extract_and_dex.gradle" +apply from: "$rootDir/android_build_files/generate_proguard.gradle" +project.afterEvaluate { + generateProguardFile('ump') + setupDexDependencies(':ump:ump_resources') + preBuild.dependsOn(':app:build') +} diff --git a/ump/integration_test/AndroidManifest.xml b/ump/integration_test/AndroidManifest.xml new file mode 100644 index 0000000000..8aa98a1958 --- /dev/null +++ b/ump/integration_test/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ump/integration_test/CMakeLists.txt b/ump/integration_test/CMakeLists.txt new file mode 100644 index 0000000000..29c6a6e4d1 --- /dev/null +++ b/ump/integration_test/CMakeLists.txt @@ -0,0 +1,242 @@ +# Copyright 2021 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. + +# Cmake file for a single C++ integration test build. + +cmake_minimum_required(VERSION 2.8) + +find_program(FIREBASE_PYTHON_EXECUTABLE + NAMES python3 python + DOC "The Python interpreter to use, such as one from a venv" + REQUIRED +) + +# User settings for Firebase integration tests. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../cpp_sdk_version.json") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") + endif() +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Copy all prerequisite files for integration tests to run. +if(NOT ANDROID) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py) + # If this is running from inside the SDK directory, run the setup script. + execute_process( + COMMAND + ${FIREBASE_PYTHON_EXECUTABLE} + "${CMAKE_CURRENT_LIST_DIR}/../../setup_integration_tests.py" + "${CMAKE_CURRENT_LIST_DIR}" + RESULT_VARIABLE + FIREBASE_PYTHON_EXECUTABLE_RESULT + ) + if(NOT FIREBASE_PYTHON_EXECUTABLE_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to run setup_integration_tests.py") + endif() + endif() +endif() + +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +project(firebase_testapp) + +# Integration test source files. +set(FIREBASE_APP_FRAMEWORK_SRCS + src/app_framework.cc + src/app_framework.h +) + +set(FIREBASE_TEST_FRAMEWORK_SRCS + src/firebase_test_framework.h + src/firebase_test_framework.cc +) + +set(FIREBASE_INTEGRATION_TEST_SRCS + src/integration_test.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Firebase C++ SDK requires C++14. +set (CMAKE_CXX_STANDARD 14) +set (CMAKE_CXX_STANDARD_REQUIRED YES) # Don't fall back to an earlier version. + +# Download and unpack googletest (and googlemock) at configure time +set(GOOGLETEST_ROOT ${CMAKE_CURRENT_LIST_DIR}/external/googletest) +# Note: Once googletest is downloaded once, it won't be updated or +# downloaded again unless you delete the "external/googletest" +# directory. +if (NOT EXISTS ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + configure_file(googletest.cmake + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/CMakeLists.txt COPYONLY) + execute_process(COMMAND ${CMAKE_COMMAND} . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/external/googletest ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() +endif() + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_APP_FRAMEWORK_ANDROID_SRCS + src/android/android_app_framework.cc + ) + + # Source files used for the Android build. + set(FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS + src/android/android_firebase_test_framework.cc + ) + + # Build native_app_glue as a static lib + add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + + # Export ANativeActivity_onCreate(), + # Refer to: https://github.com/android-ndk/ndk/issues/381. + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + + add_library(gtest STATIC + ${GOOGLETEST_ROOT}/src/googletest/src/gtest-all.cc) + target_include_directories(gtest + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include) + add_library(gmock STATIC + ${GOOGLETEST_ROOT}/src/googlemock/src/gmock-all.cc) + target_include_directories(gmock + PRIVATE ${GOOGLETEST_ROOT}/src/googletest + PRIVATE ${GOOGLETEST_ROOT}/src/googlemock + PUBLIC ${GOOGLETEST_ROOT}/src/googletest/include + PUBLIC ${GOOGLETEST_ROOT}/src/googlemock/include) + + # Define the target as a shared library, as that is what gradle expects. + set(integration_test_target_name "android_integration_test_main") + add_library(${integration_test_target_name} SHARED + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_ANDROID_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_TEST_FRAMEWORK_ANDROID_SRCS} + ) + + target_include_directories(${integration_test_target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS log android atomic native_app_glue) +else() + # Build a desktop application. + add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/external/googletest/src + ${CMAKE_CURRENT_LIST_DIR}/external/googletest/build + EXCLUDE_FROM_ALL) + + # The gtest/gtest_main targets carry header search path + # dependencies automatically when using CMake 2.8.11 or + # later. Otherwise we have to add them here ourselves. + if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") + include_directories("${gmock_SOURCE_DIR}/include") + endif() + + # Windows runtime mode, either MD or MT depending on whether you are using + # /MD or /MT. For more information see: + # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx + set(MSVC_RUNTIME_MODE MD) + + # Platform abstraction layer for the desktop integration test. + set(FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS + src/desktop/desktop_app_framework.cc + src/desktop/desktop_firebase_test_framework.cc + ) + + set(integration_test_target_name "integration_test") + add_executable(${integration_test_target_name} + ${FIREBASE_APP_FRAMEWORK_SRCS} + ${FIREBASE_APP_FRAMEWORK_DESKTOP_SRCS} + ${FIREBASE_TEST_FRAMEWORK_SRCS} + ${FIREBASE_INTEGRATION_TEST_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + else() + set(ADDITIONAL_LIBS pthread) + endif() + + # If a config file is present, copy it into the binary location so that it's + # possible to create the default Firebase app. + set(FOUND_JSON_FILE FALSE) + foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/${config}") + add_custom_command( + TARGET ${integration_test_target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_LIST_DIR}/${config}" $) + set(FOUND_JSON_FILE TRUE) + break() + endif() + endforeach() + if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") + endif() +endif() + +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) +# Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_gma firebase_app) +set(gtest_libs gtest gmock) +target_link_libraries(${integration_test_target_name} ${firebase_libs} + ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/ump/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json b/ump/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/ump/integration_test/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ump/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json b/ump/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..6f870a4629 --- /dev/null +++ b/ump/integration_test/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,51 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "subtype" : "retina4", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + }, + { + "orientation" : "landscape", + "idiom" : "ipad", + "extent" : "full-screen", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ump/integration_test/Info.plist b/ump/integration_test/Info.plist new file mode 100644 index 0000000000..953571e326 --- /dev/null +++ b/ump/integration_test/Info.plist @@ -0,0 +1,41 @@ + + + + + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + NSUserTrackingUsageDescription + This identifier will be used to deliver personalized ads to you. + CFBundleURLTypes + + + CFBundleURLSchemes + + REPLACE_WITH_REVERSED_CLIENT_ID + firebase-game-loop + firebase-ui-test + + + + + diff --git a/ump/integration_test/LaunchScreen.storyboard b/ump/integration_test/LaunchScreen.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/ump/integration_test/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ump/integration_test/LibraryManifest.xml b/ump/integration_test/LibraryManifest.xml new file mode 100644 index 0000000000..a5db8d174d --- /dev/null +++ b/ump/integration_test/LibraryManifest.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/ump/integration_test/Podfile b/ump/integration_test/Podfile new file mode 100644 index 0000000000..932579bd3b --- /dev/null +++ b/ump/integration_test/Podfile @@ -0,0 +1,18 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +# Firebase GMA test application. +use_frameworks! :linkage => :static + +target 'integration_test' do + platform :ios, '13.0' + pod 'Firebase/CoreOnly', '11.10.0' + pod 'Google-Mobile-Ads-SDK', '11.2.0' + pod 'GoogleUserMessagingPlatform', '2.3.0' +end + +post_install do |installer| + # If this is running from inside the SDK directory, run the setup script. + system("if [[ -r ../../setup_integration_tests.py ]]; then python3 ../../setup_integration_tests.py .; fi") + system("python3 ./download_googletest.py") +end + diff --git a/ump/integration_test/build.gradle b/ump/integration_test/build.gradle new file mode 100644 index 0000000000..41d156e8eb --- /dev/null +++ b/ump/integration_test/build.gradle @@ -0,0 +1,102 @@ +// Copyright 2021 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. + +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + // r8 on this version of the Android tools has a bug, + // so specify a different version to use. + classpath 'com.android.tools:r8:8.3.37' + classpath 'com.google.gms:google-services:4.4.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + mavenCentral() + } +} + +apply plugin: 'com.android.application' + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '32.0.0' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.android.admob.testapp' + minSdkVersion 23 + targetSdkVersion 34 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + multiDexEnabled true + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } + lintOptions { + abortOnError false + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + gma +} + +apply plugin: 'com.google.gms.google-services' + +task copyIntegrationTestFiles(type:Exec) { + // If this is running form inside the SDK directory, run the setup script. + if (project.file('../../setup_integration_tests.py').exists()) { + commandLine 'python3', '../../setup_integration_tests.py', project.projectDir.toString() + } + else { + commandLine 'echo', '' + } +} + +build.dependsOn(copyIntegrationTestFiles) diff --git a/ump/integration_test/empty.swift b/ump/integration_test/empty.swift new file mode 100644 index 0000000000..b637790955 --- /dev/null +++ b/ump/integration_test/empty.swift @@ -0,0 +1,9 @@ +// +// empty.swift +// integration_test +// +// Created by David Della Bitta on 5/12/22. +// Copyright © 2022 Google. All rights reserved. +// + +import Foundation diff --git a/ump/integration_test/googletest.cmake b/ump/integration_test/googletest.cmake new file mode 100644 index 0000000000..a643a3e2f2 --- /dev/null +++ b/ump/integration_test/googletest.cmake @@ -0,0 +1,35 @@ +# Copyright 2021 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. + +# Download GoogleTest from GitHub as an external project. +# Pin to 1.11.0 because we touch internal GoogleTest structures that could change in the future. + +# This CMake file is taken from: +# https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project + +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG "release-1.11.0" + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/ump/integration_test/gradle.properties b/ump/integration_test/gradle.properties new file mode 100644 index 0000000000..ac891ac594 --- /dev/null +++ b/ump/integration_test/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX = true +org.gradle.jvmargs=-Xmx2560m diff --git a/ump/integration_test/gradle/wrapper/gradle-wrapper.jar b/ump/integration_test/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z zGh0A%o~bd$iYvP!egRY{hObDIvy_vXAOkeTgl5o!33m!l4VLm@<-FwT0+k|yl~vUh z@RFcL4=b(QQQmwQ;>FS_e96dyIU`jmR%&&Amxcb8^&?wvpK{_V_IbmqHh);$hBa~S z;^ph!k~noKv{`Ix7Hi&;Hq%y3wpqUsYO%HhI3Oe~HPmjnSTEasoU;Q_UfYbzd?Vv@ zD6ztDG|W|%xq)xqSx%bU1f>fF#;p9g=Hnjph>Pp$ZHaHS@-DkHw#H&vb1gARf4A*zm3Z75QQ6l( z=-MPMjish$J$0I49EEg^Ykw8IqSY`XkCP&TC?!7zmO`ILgJ9R{56s-ZY$f> zU9GwXt`(^0LGOD9@WoNFK0owGKDC1)QACY_r#@IuE2<`tep4B#I^(PRQ_-Fw(5nws zpkX=rVeVXzR;+%UzoNa;jjx<&@ABmU5X926KsQsz40o*{@47S2 z)p9z@lt=9?A2~!G*QqJWYT5z^CTeckRwhSWiC3h8PQ0M9R}_#QC+lz>`?kgy2DZio zz&2Ozo=yTXVf-?&E;_t`qY{Oy>?+7+I= zWl!tZM_YCLmGXY1nKbIHc;*Mag{Nzx-#yA{ zTATrWj;Nn;NWm6_1#0zy9SQiQV=38f(`DRgD|RxwggL(!^`}lcDTuL4RtLB2F5)lt z=mNMJN|1gcui=?#{NfL{r^nQY+_|N|6Gp5L^vRgt5&tZjSRIk{_*y<3^NrX6PTkze zD|*8!08ZVN)-72TA4Wo3B=+Rg1sc>SX9*X>a!rR~ntLVYeWF5MrLl zA&1L8oli@9ERY|geFokJq^O$2hEpVpIW8G>PPH0;=|7|#AQChL2Hz)4XtpAk zNrN2@Ju^8y&42HCvGddK3)r8FM?oM!3oeQ??bjoYjl$2^3|T7~s}_^835Q(&b>~3} z2kybqM_%CIKk1KSOuXDo@Y=OG2o!SL{Eb4H0-QCc+BwE8x6{rq9j$6EQUYK5a7JL! z`#NqLkDC^u0$R1Wh@%&;yj?39HRipTeiy6#+?5OF%pWyN{0+dVIf*7@T&}{v%_aC8 zCCD1xJ+^*uRsDT%lLxEUuiFqSnBZu`0yIFSv*ajhO^DNoi35o1**16bg1JB z{jl8@msjlAn3`qW{1^SIklxN^q#w|#gqFgkAZ4xtaoJN*u z{YUf|`W)RJfq)@6F&LfUxoMQz%@3SuEJHU;-YXb7a$%W=2RWu5;j44cMjC0oYy|1! zed@H>VQ!7=f~DVYkWT0nfQfAp*<@FZh{^;wmhr|K(D)i?fq9r2FEIatP=^0(s{f8GBn<8T zVz_@sKhbLE&d91L-?o`13zv6PNeK}O5dv>f{-`!ms#4U+JtPV=fgQ5;iNPl9Hf&9( zsJSm5iXIqN7|;I5M08MjUJ{J2@M3 zYN9ft?xIjx&{$K_>S%;Wfwf9N>#|ArVF^shFb9vS)v9Gm00m_%^wcLxe;gIx$7^xR zz$-JDB|>2tnGG@Rrt@R>O40AreXSU|kB3Bm)NILHlrcQ&jak^+~b`)2;otjI(n8A_X~kvp4N$+4|{8IIIv zw*(i}tt+)Kife9&xo-TyoPffGYe;D0a%!Uk(Nd^m?SvaF-gdAz4~-DTm3|Qzf%Pfd zC&tA;D2b4F@d23KV)Csxg6fyOD2>pLy#n+rU&KaQU*txfUj&D3aryVj!Lnz*;xHvl zzo}=X>kl0mBeSRXoZ^SeF94hlCU*cg+b}8p#>JZvWj8gh#66A0ODJ`AX>rubFqbBw z-WR3Z5`33S;7D5J8nq%Z^JqvZj^l)wZUX#7^q&*R+XVPln{wtnJ~;_WQzO{BIFV55 zLRuAKXu+A|7*2L*<_P${>0VdVjlC|n^@lRi}r?wnzQQm z3&h~C3!4C`w<92{?Dpea@5nLP2RJrxvCCBh%Tjobl2FupWZfayq_U$Q@L%$uEB6#X zrm_1TZA8FEtkd`tg)a_jaqnv3BC_O*AUq-*RNLOT)$>2D!r>FZdH&$x5G_FiAPaw4 zgK*7>(qd6R?+M3s@h>Z|H%7eGPxJWn_U$w`fb(Mp+_IK2Kj37YT#Xe5e6KS-_~mW} z`NXEovDJh7n!#q4b+=ne<7uB7Y2(TAR<3@PS&o3P$h#cZ-xF$~JiH6_gsv9v(#ehK zhSB_#AI%lF#+!MB5DMUN+Zhf}=t~{B|Fn{rGM?dOaSvX!D{oGXfS*%~g`W84JJAy4 zMdS?9Bb$vx?`91$J`pD-MGCTHNxU+SxLg&QY+*b_pk0R=A`F}jw$pN*BNM8`6Y=cm zgRh#vab$N$0=XjH6vMyTHQg*+1~gwOO9yhnzZx#e!1H#|Mr<`jJGetsM;$TnciSPJ z5I-R0)$)0r8ABy-2y&`2$33xx#%1mp+@1Vr|q_e=#t7YjjWXH#3F|Fu<G#+-tE2K7 zOJkYxNa74@UT_K4CyJ%mR9Yfa$l=z}lB(6)tZ1Ksp2bv$^OUn3Oed@=Q0M}imYTwX zQoO^_H7SKzf_#kPgKcs%r4BFUyAK9MzfYReHCd=l)YJEgPKq-^z3C%4lq%{&8c{2CGQ3jo!iD|wSEhZ# zjJoH87Rt{4*M_1GdBnBU3trC*hn@KCFABd=Zu`hK;@!TW`hp~;4Aac@24m|GI)Ula z4y%}ClnEu;AL4XVQ6^*!()W#P>BYC@K5mw7c4X|Hk^(mS9ZtfMsVLoPIiwI?w_X0- z#vyiV5q9(xq~fS`_FiUZw->8Awktga>2SrWyvZ|h@LVFtnY#T z%OX30{yiSov4!43kFd(8)cPRMyrN z={af_ONd;m=`^wc7lL|b7V!;zmCI}&8qz=?-6t=uOV;X>G{8pAwf9UJ`Hm=ubIbgR zs6bw3pFeQHL`1P1m5fP~fL*s?rX_|8%tB`Phrij^Nkj{o0oCo*g|ELexQU+2gt66=7}w5A+Qr}mHXC%)(ODT# zK#XTuzqOmMsO~*wgoYjDcy)P7G`5x7mYVB?DOXV^D3nN89P#?cp?A~c%c$#;+|10O z8z(C>mwk#A*LDlpv2~JXY_y_OLZ*Mt)>@gqKf-Ym+cZ{8d%+!1xNm3_xMygTp-!A5 zUTpYFd=!lz&4IFq)Ni7kxLYWhd0o2)ngenV-QP@VCu;147_Lo9f~=+=Nw$6=xyZzp zn7zAe41Sac>O60(dgwPd5a^umFVSH;<7vN>o;}YlMYhBZFZ}-sz`P^3oAI>SCZy&zUtwKSewH;CYysPQN7H>&m215&e2J? zY}>5N-LhaDeRF~C0cB>M z7@y&xh9q??*EIKnh*;1)n-WuSl6HkrI?OUiS^lx$Sr2C-jUm6zhd{nd(>#O8k9*kF zPom7-%w1NjFpj7WP=^!>Vx^6SG^r`r+M&s7V(uh~!T7aE;_ubqNSy)<5(Vi)-^Mp9 zEH@8Vs-+FEeJK%M0z3FzqjkXz$n~BzrtjQv`LagAMo>=?dO8-(af?k@UpL5J#;18~ zHCnWuB(m6G6a2gDq2s`^^5km@A3Rqg-oHZ68v5NqVc zHX_Iw!OOMhzS=gfR7k;K1gkEwuFs|MYTeNhc0js>Wo#^=wX4T<`p zR2$8p6%A9ZTac;OvA4u#Oe3(OUep%&QgqpR8-&{0gjRE()!Ikc?ClygFmGa(7Z^9X zWzmV0$<8Uh)#qaH1`2YCV4Zu6@~*c*bhtHXw~1I6q4I>{92Eq+ZS@_nSQU43bZyidk@hd$j-_iL=^^2CwPcaXnBP;s;b zA4C!k+~rg4U)}=bZ2q*)c4BZ#a&o!uJo*6hK3JRBhOOUQ6fQI;dU#3v>_#yi62&Sp z-%9JJxwIfQ`@w(_qH0J0z~(lbh`P zHoyp2?Oppx^WXwD<~20v!lYm~n53G1w*Ej z9^B*j@lrd>XGW43ff)F;5k|HnGGRu=wmZG9c~#%vDWQHlOIA9(;&TBr#yza{(?k0> zcGF&nOI}JhuPl`kLViBEd)~p2nY9QLdX42u9C~EUWsl-@CE;05y@^V1^wM$ z&zemD1oZd$Z))kEw9)_Mf+X#nT?}n({(+aXHK2S@j$MDsdrw-iLb?#r{?Vud?I5+I zVQ8U?LXsQ}8-)JBGaoawyOsTTK_f8~gFFJ&lhDLs8@Rw$ey-wr&eqSEU^~1jtHmz6 z!D2g4Yh?3VE*W8=*r&G`?u?M~AdO;uTRPfE(@=Gkg z7gh=EGu!6VJJ?S_>|5ZwY?dGFBp3B9m4J1=7u=HcGjsCW+y6`W?OWxfH?S#X8&Zk& zvz6tWcnaS1@~3FTH}q_*$)AjYA_j;yl0H0{I(CW7Rq|;5Q2>Ngd(tmJDp+~qHe_8y zPU_fiCrn!SJ3x&>o6;WDnjUVEt`2fhc9+uLI>99(l$(>Tzwpbh>O775OA5i`jaBdp zXnCwUgomyF3K$0tXzgQhSAc!6nhyRh_$fP}Rd$|*Y7?ah(JrN=I7+)+Hp4BLJJ2P~ zFD!)H^uR2*m7GQZpLUVS#R3^?2wCd}(gcFcz!u5KN9ldNJdh@%onf06z9m~T0n;dqg6@?>G@S|rPO*Kj>{su+R|7bH>osA&uD4eqxtr**k($ii`uO? z7-&VkiL4Rp3S&e+T}2Z#;NtWHZco(v8O3QMvN0g7l8GV|U2>x-DbamkZo5)bjaSFR zr~Y9(EvF9{o*@|nBPj+e5o$_K`%TH1hD=|its}|qS^o6EQu_gOuDUH=Dtzik;P7G$ zq%_T<>9O}bGIB?;IQ*H`BJ5NWF6+XLv@G7aZwcy(&BoepG~u`aIcG>y+;J7+L=wTZ zB=%n@O}=+mjBO%1lMo6C0@1*+mhBqqY((%QMUBhyeC~r*5WVqzisOXFncr*5Lr0q6 zyPU&NOV}Vt2jl>&yig4I6j93?D>Ft=keRh=Y;3*^Z-I26nkZ#Jj5OJ89_?@#9lNjp z#gfAO6i937)~I|98P%xAWxwmk(F&@lTMx63*FZ~2b{NHU+}EV8+kMAB0bM*Zn#&7ubt98!PT^ZcMOfwMgkYz6+;?CKbvV zQ}Z@s_3JcMPhF&y1?}9uZFIBiPR3g7lf=+XEr9Bl%zRfGcaKb*ZQq5b35ZkR@=JEw zP#iqgh2^#@VA-h)>r`7R-$1_ddGr&oWWV$rx;pkG0Yohp9p@In_p)hKvMo@qIv zcN2t{23&^Nj=Y&gX;*vJ;kjM zHE2`jtjVRRn;=WqVAY&m$z=IoKa{>DgJ;To@OPqNbh=#jiS$WE+O4TZIOv?niWs47 zQfRBG&WGmU~>2O{}h17wXGEnigSIhCkg%N~|e?hG8a- zG!Wv&NMu5z!*80>;c^G9h3n#e>SBt5JpCm0o-03o2u=@v^n+#6Q^r#96J5Q=Dd=>s z(n0{v%yj)=j_Je2`DoyT#yykulwTB+@ejCB{dA7VUnG>4`oE?GFV4sx$5;%9&}yxfz<-wWk|IlA|g&! zN_Emw#w*2GT=f95(%Y1#Viop;Yro3SqUrW~2`Fl?Ten{jAt==a>hx$0$zXN`^7>V_ zG*o7iqeZV)txtHUU2#SDTyU#@paP;_yxp!SAG##cB= zr@LoQg4f~Uy5QM++W`WlbNrDa*U;54`3$T;^YVNSHX4?%z|`B~i7W+kl0wBB`8|(l zAyI6dXL&-Sei0=f#P^m`z=JJ`=W;PPX18HF;5AaB%Zlze`#pz;t#7Bzq0;k8IyvdK=R zBW+4GhjOv+oNq^~#!5(+pDz)Ku{u60bVjyym8Or8L;iqR|qTcxEKTRm^Y%QjFYU=ab+^a|!{!hYc+= z%Qc02=prKpzD+jiiOwzyb(dELO|-iyWzizeLugO!<1(j|3cbR!8Ty1$C|l@cWoi?v zLe<5+(Z-eH++=fX**O-I8^ceYZgiA!!dH+7zfoP-Q+@$>;ab&~cLFg!uOUX7h0r== z`@*QP9tnV1cu1!9pHc43C!{3?-GUBJEzI(&#~vY9MEUcRNR*61)mo!RG>_Yb^rNN7 zR9^bI45V?3Lq`^^BMD!GONuO4NH#v9OP3@s%6*Ha3#S*;f z6JEi)qW#Iq#5BtIXT9Gby|H?NJG}DN#Li82kZ_Rt1=T0Z@U6OAdyf}4OD|Sk^2%-1 zzgvqZ@b6~kL!^sZLO$r{s!3fQ5bHW}8r$uTVS*iw1u8^9{YlPp_^Xm5IN zF|@)ZOReX zB*#tEbWEX~@f)ST|s$oUKS@drycE1tYtdJ9b*(uFTxNZ{n3BI*kF7wXgT6+@PI@vwH7iQS{1T!Nauk>fm8gOLe`->Pi~ z8)3=UL_$OLl2n7QZlHt846nkYFu4V};3LpYA%5VaF#a2#d2g0&ZO~3WA%1XlerVpg zCAlM;(9OqH@`(>Tha{*@R%twB!}1ng4V=^+R`Q{#fkRk)C|suozf-uCXrkIH2SC^C z6wlxR`yS;-U#uu#`OnD%U<41%C4mp>LYLPIbgVO~WsT1if)Y)T*8nUB`2*(B;U_ha1NWv2`GqrZ z3MWWpT3tZ!*N@d*!j3=@K4>X*gX4A^@QPAz24?7u90AXaLiFq=Z$|5p$Ok2|YCX_Z zFgNPiY2r_Bg2BQE!0z=_N*G?%0cNITmAru*!Mws=F+F&Qw!&1?DBN{vSy%IvGRV@1 zS->PARgL^XS!-aZj zi@`~LhWfD!H-L0kNv=Jil9zR0>jZLqu)cLq?$yXVyk%EteKcWbe^qh#spHJPa#?92 za(N(Kw0se^$7nQUQZBet;C_Dj5(2_?TdrXFYwmebq}YGQbN5Ex7M zGSCX~Ey;5AqAzEDNr%p^!cuG?&wIeY&Bm5guVg>8F=!nT%7QZTGR(uGM&IZuMw0V_ zhPiIFWm?H?aw*(v6#uVT@NEzi2h5I$cZ-n0~m$tmwdMTjG*of^Y%1 zW?Y%o*-_iMqEJhXo^!Qo?tGFUn1Mb|urN4_;a)9bila2}5rBS#hZ5wV+t1xbyF1TW zj+~cdjbcMgY$zTOq6;ODaxzNA@PZIXX(-=cT8DBd;9ihfqqtbDr9#gXGtK24BPxjZ z9+Xp>W1(s)->-}VX~BoQv$I|-CBdO`gULrvNL>;@*HvTdh@wyNf}~IB5mFnTitX2i z;>W>tlQyc2)T4Mq+f!(i3#KuK-I8Kj3Wm(UYx?KWWt8DEPR_Jdb9CE~Fjc7Rkh#gh zowNv()KRO@##-C+ig0l!^*ol!Bj%d32_N*~d!|&>{t!k3lc?6VrdlCCb1?qyoR42m zv;4KdwCgvMT*{?tJKa(T?cl|b;k4P>c&O@~g71K5@}ys$)?}WSxD;<5%4wEz7h=+q ztLumn6>leWdDk#*@{=v9p)MsvuJMyf_VEs;pJh?i3z7_W@Q|3p$a}P@MQ-NpMtDUBgH!h4Ia#L&POr4Qw0Tqdw^}gCmQAB z8Dgkzn?V!_@04(cx0~-pqJOpeP1_}@Ml3pCb45EJoghLows9ET13J8kt0;m$6-jO( z4F|p+JFD1NT%4bpn4?&)d+~<360$z5on`eS6{H`S>t`VS$>(D`#mC*XK6zULj1Da# zpV$gw$2Ui{07NiYJQQNK;rOepRxA>soNK~B2;>z;{Ovx`k}(dlOHHuNHfeR}7tmIp zcM}q4*Fq8vSNJYi@4-;}`@bC?nrUy`3jR%HXhs79qWI5;hyTpH5%n-NcKu&j(aGwT z1~{geeq?Jd>>HL+?2`0K8dB2pvTS=LO~tb~vx_<=iN8^rW!y@~lBTAaxHmvVQJSeJ z!cb9ffMdP1lgI=>QJN{XpM4{reRrdIt|v|0-8!p}M*Qw^uV1@Ho-YsNd0!a(os$F* zT0tGHA#0%u0j*%S>kL*73@~7|iP;;!JbWSTA@`#VHv_l_%Z7CgX@>dhg_ zgn0|U)SY~U-E5{QiT@(uPp#1jaz!(_3^Cbz2 z4ZgWWz=PdGCiGznk{^4TBfx_;ZjAHQ>dB4YI}zfEnTbf60lR%=@VWt0yc=fd38Ig* z)Q38#e9^+tA7K}IDG5Z~>JE?J+n%0_-|i2{E*$jb4h?|_^$HRHjVkiyX6@Y+)0C2a zA+eegpT1dUpqQFIwx;!ayQcWQBQTj1n5&h<%Lggt@&tE19Rm~Rijtqw6nmYip_xg0 zO_IYpU304embcWP+**H|Z5~%R*mqq+y{KbTVqugkb)JFSgjVljsR{-c>u+{?moCCl zTL)?85;LXk0HIDC3v*|bB-r_z%zvL6Dp__L*A~Z*o?$rm>cYux&)W=6#+Cb}TF&Kd zdCgz3(ZrNA>-V>$C{a^Y^2F!l_%3lFe$s(IOfLBLEJ4Mcd!y&Ah9r)7q?oc z5L(+S8{AhZ)@3bw0*8(}Xw{94Vmz6FrK&VFrJN;xB96QmqYEibFz|yHgUluA-=+yS}I-+#_Pk zN67-#8W(R^e7f!;i0tXbJgMmJZH%yEwn*-}5ew13D<_FYWnt?{Mv1+MI~u;FN~?~m z{hUnlD1|RkN}c1HQ6l@^WYbHAXPJ^m0te1woe;LDJ}XEJqh1tPf=sD0%b+OuR1aCoP>I>GBn4C24Zu$D)qg=gq;D??5 zUSj%;-Hvk_ffj-+SI{ZCp`gZcNu=L@_N}kCcs?TyMr-37fhy$?a<7lt1`fZw<%$8@B6(Wgo!#!z9z{ab|x`+&;kP!(gfdY}A-GP&4Cbh-S< z1(kmgnMyB2z3ipEj5;4<{(=&<7a>A_Jl`ujUKYV@%k(oD=cD7W@8~5O=R*zdjM_y; zXwme~0wo0aDa~9rDnjF=B}Bbj|DHRQjN|?@(F^=bVFdr!#mwr|c0843k>%~5J|7|v zSY=T)iPU6rEAwrM(xTZwPio%D4y9Z4kL0bMLKvu4yd)0ZJA3<;>a2q~rEfcREn}~1 zCJ~3c?Afvx?3^@+!lnf(kB6YwfsJ*u^y7kZA?VmM%nBmaMspWu?WXq4)jQsq`9EbT zlF2zJ)wXuAF*2u|yd5hNrG>~|i}R&ZyeetTQ!?Hz6xGZZb3W6|vR>Hq=}*m=V=Lsp zUOMxh;ZfP4za~C{Ppn^%rhitvpnu^G{Z#o-r?TdEgSbtK_+~_iD49xM;$}X*mJF02|WBL{SDqK9}p4N!G$3m=x#@T+4QcapM{4j|Q zwO!(hldpuSW#by!zHEP@tzIC|KdD z%BJzQ7Ho1(HemWm`Z8m_D#*`PZ-(R%sZmPrS$aHS#WPjH3EDitxN|DY+ zYC|3S?PQ3NNYau$Qk8f>{w}~xCX;;CE=7;Kp4^xXR8#&^L+y-jep7oO^wnQ840tg1 zuN17QKsfdqZPlB8OzwF+)q#IsmenEmIbRAJHJ$JjxzawKpk8^sBm3iy=*kB%LppNb zhSdk`^n?01FKQ;=iU+McN7Mk0^`KE>mMe1CQ2a_R26_}^$bogFm=2vqJake7x)KN( zYz;gRPL+r4*KD>1U+DU+1jh{mT8#P#(z9^(aDljpeN{mRmx{AZX&hXKXNuxj3x*RrpjvOaZ#`1EqK!$+8=0yv8}=;>f=E?5tGbRUd4%?QL zy$kq6mZeF%k6E1&8nwAYMd!-lRkhQTob$7s`*XqcHs;l~mHV}fx&0I&i!CHaPVSM{ zHdRh7a>hP)t@YTrWm9y zl-ENWSVzlKVvTdWK>)enmGCEw(WYS=FtY{srdE{Z(3~4svwd)ct;`6Y{^qiW+9E@A ztzd?lj5F#k`=E1U-n*1JJc0{x{0q!_tkD<_S6bGsW)^RxGu%Rj^Mvw|R0WP1SqvAI zs(MiAd@Y5x!UKu376&|quQNxir;{Iz(+}3k-GNb29HaQh?K30u=6sXpIc?j0hF{VY zM$Do*>pN)eRljAOgpx7fMfSrnZ7>fi@@>Jh;qxj1#-Vj}JC3E^GCbC(r55_AG>6cq z4ru34FtVuBt)bkX4>ZFWjToyu)VA>IE6hXc+^(3ruUaKRqHnx3z)(GXetm;^0D95s zQ&drwfjhM4*|q=;i5Io0eDf?I{p}qo@7i7abHX5qLu~VDwYf4bmV~-^M_U?DL(+cG z{AyE^a|*73Ft)o5k-p)+GLXj#q01VlJ9#ZJkf|+c%6qfRgVp&6NsU3~F?!uh}HJm73xq>v$h zYoW3wJE6n9P|;{8U<^%UE2wjR4x^G_Nc$J(i)!>;g4`CCh2z^Dth#ah#<`#axDR?F z4>~hnN2%B2ZUuU6j>m1Qjj~5jQSdA&Q#7hOky#=Ue)}7LPJ!8nbZO_0Sw{G>>M7&E zb1dy|0Zi$(ubk`4^XkVI%4WIpe?Bh!D~IjvZs14yHw=aQ8-`N-=P*?Kzi&eRGZ_6Z zT>eis`!Dy3eT3=vt#Lbc+;}i5XJf7zM3QneL{t?w=U<1rk7+z2Cu^|~=~54tAeSYF zsXHsU;nM0dpK>+71yo(NFLV-^Lf7%U?Q$*q{^j04Gl71ya2)^j`nmJ$cmI9eFMjp+ z#)jKmi4lZc<;l>!={@jTm%?!5jS;6;c*Ml55~r6Y?22B^K3bPhKQ(ICc&z%w<4W1= zjTTtz_}IA$%kCqU)h#$!Yq>>2mVG}qYL}!avmCWYV}x4!YEeq)pgTp| zR;+skHuc7YXRLrcbYXt>?@pa{l^2pL>RrZ!22zMmi1ZR?nkaWF*`@XFK4jGh&Em3vn(l z3~^Q9&tM^eV=f^lccCUc9v02z%^n5VV6s$~k0uq5B#Ipd6`M1Kptg^v<2jiNdlAWQ z_MmtNEaeYIHaiuaFQdG&df7miiB5lZkSbg&kxY*Eh|KTW`Tk~VwKC~+-GoYE+pvwc{+nIEizq6!xP>7ZQ(S2%48l$Y98L zvs7s<&0ArXqOb*GdLH0>Yq-f!{I~e~Z@FUIPm?jzqFZvz9VeZLYNGO}>Vh<=!Er7W zS!X6RF^et7)IM1pq57z*^hP5w7HKSDd8jHX!*gkKrGc-GssrNu5H%7-cNE{h$!aEQK3g*qy;= z)}pxO8;}nLVYm_24@iEs8)R7i;Th0n4->&$8m6(LKCRd(yn7KY%QHu_f=*#e`H^U( z{u!`9JaRD?Z?23fEXrjx>A@+a!y-_oaDB)o@2s{2%A97-ctFfrN0cXQ@6aGH`X~Nr z144?qk;MzDU-cgQOLfT3-ZR#hKmYtKG*iGf4ZJ`|`9!^SkBDUUSJCba)>mM!)k~(z zdjUqB`)~!UObMHB1b$UItM$<0kwlqHH;c z=)+~bkOcIT7vI0Iy(wD)vsg9|oi##%Rgrq`Ek;pN)}lbpz`iv{F4K*{ZZ?Zjixxxr zY|SPl2NsXH+5pimj+MvbZ_+HrfvdC13|9Zs)Y=nW$z<0mhl}%irBSm5T3ZrN#2AhY z_ZrTmS(L`U#y}VZ@~QL9wUS6AnU*7LWS02Xyz`b>%rTml#Wb0yr>@c(Ym*40g;P{V zjV1XSHdU>oY!&Jh7MzhzUV8(9E+yl5UJYga>=0Ldjwtc`5!1>LxaB-kVW;IlSPs+0 zUBx=m8OKVp<`frNvMK>WMO(iKY%PuvqD+PK*vP6f?_o!O)MCW5Ic zv(%f5PLHyOJ2h@Yn_to@54Yq;fdoy40&sbe3A$4uUXHsHP_~K}h#)p&TyOx(~JE?y(IBAQKl}~VQjVC-c6oZwmESL;`Xth?2)-b6ImNcJi z;w|`Q*k?`L(+Dp}t(FocvzWB(%~9$EAB6_J6CrA}hMj-Vy*6iA$FdV}!lvk%6}M)4 zTf<)EbXr9^hveAav1yA?>O0aNEpv0&rju{(Gt|dP=AP%)uQm~OE7@+wEhILrRLt&E zoEsF^nz>4yK1|EOU*kM+9317S;+bb7?TJM2UUpc!%sDp}7!<`i=W!ot8*C&fpj>mk#qt~GCeqcy)?W6sl>eUnR%yCBR&Ow-rc|q;lhnI+f-%`6Xf)% zIYZru;27%vA{Qi2=J`PQC<28;tFx(V^sgXf>)8WNxxQwT14M9I6- z+V0@tiCiDkv`7r-06sJS8@s|Lf>mV+8h}SPT4ZGPSMaFK7_SMXH$3KN7b2V?iV-jA zh1!Z>2tv^HVbHnNUAf-wQW#zMV(h8=3x2Swd|-%AczEIWLcm~EAu7rc3s%56b;7ME zj}$pe#fc^314Mb9i)xH^_#({)tTD4hsoz!7XcHUh9*G|}?k=D?9LBkTm2?fgaIG(%%$DL#}a-_990rQBU+M;jrf zCcvgM`+oyZmsUqc?lly9axZfO)02l$TMS#I+jHYY`Uk!gtDv|@GBQ||uaG^n*QR3Q z@tV?D;R;KmkxSDQh<2DkDC1?m?jTvf2i^T;+}aYhzL?ymNZmdns2e)}2V>tDCRw{= zTV3q3ZQDkdZQHi3?y{@8Y@1!SZQHi(y7|qSx$~Vl=iX<2`@y3eSYpsBV zI`Q-6;)B=p(ZbX55C*pu1C&yqS|@Pytis3$VDux0kxKK}2tO&GC;cH~759o?W2V)2 z)`;U(nCHBE!-maQz%z#zoRNpJR+GmJ!3N^@cA>0EGg?OtgM_h|j1X=!4N%!`g~%hdI3%yz&wq4rYChPIGnSg{H%i>96! z-(@qsCOfnz7ozXoUXzfzDmr>gg$5Z1DK$z#;wn9nnfJhy6T5-oi9fT^_CY%VrL?l} zGvnrMZP_P|XC$*}{V}b^|Hc38YaZQESOWqA1|tiXKtIxxiQ%Zthz?_wfx@<8I{XUW z+LH%eO9RxR_)8gia6-1>ZjZB2(=`?uuX|MkX082Dz*=ep%hMwK$TVTyr2*|gDy&QOWu zorR#*(SDS{S|DzOU$<-I#JTKxj#@0(__e&GRz4NuZZLUS8}$w+$QBgWMMaKge*2-) zrm62RUyB?YSUCWTiP_j-thgG>#(ZEN+~bMuqT~i3;Ri`l${s0OCvCM>sqtIX?Cy`8 zm)MRz-s^YOw>9`aR#J^tJz6$S-et%elmR2iuSqMd(gr6a#gA_+=N(I6%Cc+-mg$?_1>PlK zbgD2`hLZ?z4S~uhJf=rraLBL?H#c$cXyqt{u^?#2vX2sFb z^EU-9jmp{IZ~^ii@+7ogf!n_QawvItcLiC}w^$~vgEi(mX79UwDdBg`IlF42E5lWE zbSibqoIx*0>WWMT{Z_NadHkSg8{YW4*mZ@6!>VP>ey}2PuGwo%>W7FwVv7R!OD32n zW6ArEJX8g_aIxkbBl^YeTy5mhl1kFGI#n>%3hI>b(^`1uh}2+>kKJh0NUC|1&(l)D zh3Barl&yHRG+Le2#~u>KoY-#GSF>v)>xsEp%zgpq4;V6upzm3>V&yk^AD}uIF{vIn zRN-^d4(Sk6ioqcK@EObsAi#Z-u&Hh#kZdv1rjm4u=$2QF<6$mgJ4BE0yefFI zT7HWn?f668n!;x>!CrbdA~lDfjX?)315k1fMR~lG)|X_o()w|NX&iYUTKxI2TLl|r z{&TWcBxP>*;|XSZ1GkL&lSg?XL9rR4Ub&4&03kf};+6$F)%2rsI%9W_i_P|P%Z^b@ zDHH2LV*jB@Izq0~E4F^j04+C|SFiV8{!bth%bz(KfCg42^ zGz5P7xor$)I4VX}Cf6|DqZ$-hG7(}91tg#AknfMLFozF1-R~KS3&5I0GNb`P1+hIB z?OPmW8md3RB6v#N{4S5jm@$WTT{Sg{rVEs*)vA^CQLx?XrMKM@*gcB3mk@j#l0(~2 z9I=(Xh8)bcR(@8=&9sl1C?1}w(z+FA2`Z^NXw1t(!rpYH3(gf7&m=mm3+-sls8vRq z#E(Os4ZNSDdxRo&`NiRpo)Ai|7^GziBL6s@;1DZqlN@P_rfv4Ce1={V2BI~@(;N`A zMqjHDayBZ);7{j>)-eo~ZwBHz0eMGRu`43F`@I0g!%s~ANs>Vum~RicKT1sUXnL=gOG zDR`d=#>s?m+Af1fiaxYxSx{c5@u%@gvoHf#s6g>u57#@#a2~fNvb%uTYPfBoT_$~a^w96(}#d;-wELAoaiZCbM zxY4fKlS6-l1!b1!yra|`LOQoJB))=CxUAYqFcTDThhA?d}6FD$gYlk**!# zD=!KW>>tg1EtmSejwz{usaTPgyQm~o+NDg`MvNo)*2eWX*qAQ)4_I?Pl__?+UL>zU zvoT(dQ)pe9z1y}qa^fi-NawtuXXM>*o6Al~8~$6e>l*vX)3pB_2NFKR#2f&zqbDp7 z5aGX%gMYRH3R1Q3LS91k6-#2tzadzwbwGd{Z~z+fBD5iJ6bz4o1Rj#7cBL|x8k%jO z{cW0%iYUcCODdCIB(++gAsK(^OkY5tbWY;)>IeTp{{d~Y#hpaDa-5r#&Ha?+G{tn~ zb(#A1=WG1~q1*ReXb4CcR7gFcFK*I6Lr8bXLt9>9IybMR&%ZK15Pg4p_(v5Sya_70 ziuUYG@EBKKbKYLWbDZ)|jXpJJZ&bB|>%8bcJ7>l2>hXuf-h5Bm+ zHZ55e9(Sg>G@8a`P@3e2(YWbpKayoLQ}ar?bOh2hs89=v+ifONL~;q(d^X$7qfw=; zENCt`J*+G;dV_85dL3Tm5qz2K4m$dvUXh>H*6A@*)DSZ2og!!0GMoCPTbcd!h z@fRl3f;{F%##~e|?vw6>4VLOJXrgF2O{)k7={TiDIE=(Dq*Qy@oTM*zDr{&ElSiYM zp<=R4r36J69aTWU+R9Hfd$H5gWmJ?V){KU3!FGyE(^@i!wFjeZHzi@5dLM387u=ld zDuI1Y9aR$wW>s#I{2!yLDaVkbP0&*0Rw%6bi(LtieJQ4(1V!z!ec zxPd)Ro0iU%RP#L|_l?KE=8&DRHK>jyVOYvhGeH+Dg_E%lgA(HtS6e$v%D7I;JSA2x zJyAuin-tvpN9g7>R_VAk2y;z??3BAp?u`h-AVDA;hP#m+Ie`7qbROGh%_UTW#R8yfGp<`u zT0}L)#f%(XEE)^iXVkO8^cvjflS zqgCxM310)JQde*o>fUl#>ZVeKsgO|j#uKGi)nF_ur&_f+8#C0&TfHnfsLOL|l(2qn zzdv^wdTi|o>$q(G;+tkTKrC4rE)BY?U`NHrct*gVx&Fq2&`!3htkZEOfODxftr4Te zoseFuag=IL1Nmq45nu|G#!^@0vYG5IueVyabw#q#aMxI9byjs99WGL*y)AKSaV(zx z_`(}GNM*1y<}4H9wYYSFJyg9J)H?v((!TfFaWx(sU*fU823wPgN}sS|an>&UvI;9B(IW(V)zPBm!iHD} z#^w74Lpmu7Q-GzlVS%*T-z*?q9;ZE1rs0ART4jnba~>D}G#opcQ=0H)af6HcoRn+b z<2rB{evcd1C9+1D2J<8wZ*NxIgjZtv5GLmCgt?t)h#_#ke{c+R6mv6))J@*}Y25ef z&~LoA&qL-#o=tcfhjH{wqDJ;~-TG^?2bCf~s0k4Rr!xwz%Aef_LeAklxE=Yzv|3jf zgD0G~)e9wr@)BCjlY84wz?$NS8KC9I$wf(T&+79JjF#n?BTI)Oub%4wiOcqw+R`R_q<`dcuoF z%~hKeL&tDFFYqCY)LkC&5y(k7TTrD>35rIAx}tH4k!g9bwYVJ>Vdir4F$T*wC@$08 z9Vo*Q0>*RcvK##h>MGUhA9xix+?c1wc6xJhn)^9;@BE6i*Rl8VQdstnLOP1mq$2;!bfASHmiW7|=fA{k$rs^-8n{D6_ z!O0=_K}HvcZJLSOC6z-L^pl3Gg>8-rU#Sp1VHMqgXPE@9x&IHe;K3;!^SQLDP1Gk&szPtk| z!gP;D7|#y~yVQ?sOFiT*V(Z-}5w1H6Q_U5JM#iW16yZiFRP1Re z6d4#47#NzEm};1qRP9}1;S?AECZC5?6r)p;GIW%UGW3$tBN7WTlOy|7R1?%A<1!8Z zWcm5P6(|@=;*K&3_$9aiP>2C|H*~SEHl}qnF*32RcmCVYu#s!C?PGvhf1vgQ({MEQ z0-#j>--RMe{&5&$0wkE87$5Ic5_O3gm&0wuE-r3wCp?G1zA70H{;-u#8CM~=RwB~( zn~C`<6feUh$bdO1%&N3!qbu6nGRd5`MM1E_qrbKh-8UYp5Bn)+3H>W^BhAn;{BMii zQ6h=TvFrK)^wKK>Ii6gKj}shWFYof%+9iCj?ME4sR7F+EI)n8FL{{PKEFvB65==*@ ztYjjVTJCuAFf8I~yB-pN_PJtqH&j$`#<<`CruB zL=_u3WB~-;t3q)iNn0eU(mFTih<4nOAb>1#WtBpLi(I)^zeYIHtkMGXCMx+I zxn4BT0V=+JPzPeY=!gAL9H~Iu%!rH0-S@IcG%~=tB#6 z3?WE7GAfJ{>GE{?Cn3T!QE}GK9b*EdSJ02&x@t|}JrL{^wrM@w^&})o;&q816M5`} zv)GB;AU7`haa1_vGQ}a$!m-zkV(+M>q!vI0Swo18{;<>GYZw7-V-`G#FZ z;+`vsBihuCk1RFz1IPbPX8$W|nDk6yiU8Si40!zy{^nmv_P1=2H*j<^as01|W>BQS zU)H`NU*-*((5?rqp;kgu@+hDpJ;?p8CA1d65)bxtJikJal(bvzdGGk}O*hXz+<}J? zLcR+L2OeA7Hg4Ngrc@8htV!xzT1}8!;I6q4U&S$O9SdTrot<`XEF=(`1{T&NmQ>K7 zMhGtK9(g1p@`t)<)=eZjN8=Kn#0pC2gzXjXcadjHMc_pfV(@^3541)LC1fY~k2zn&2PdaW`RPEHoKW^(p_b=LxpW&kF?v&nzb z1`@60=JZj9zNXk(E6D5D}(@k4Oi@$e2^M%grhlEuRwVGjDDay$Qpj z`_X-Y_!4e-Y*GVgF==F0ow5MlTTAsnKR;h#b0TF>AyJe`6r|%==oiwd6xDy5ky6qQ z)}Rd0f)8xoNo)1jj59p;ChIv4Eo7z*{m2yXq6)lJrnziw9jn%Ez|A-2Xg4@1)ET2u zIX8`u5M4m=+-6?`S;?VDFJkEMf+=q?0D7?rRv)mH=gptBFJGuQo21rlIyP>%ymGWk z=PsJ>>q~i>EN~{zO0TklBIe(8i>xkd=+U@;C{SdQ`E03*KXmWm4v#DEJi_-F+3lrR z;0al0yXA&axWr)U%1VZ@(83WozZbaogIoGYpl!5vz@Tz5?u36m;N=*f0UY$ssXR!q zWj~U)qW9Q9Fg9UW?|XPnelikeqa9R^Gk77PgEyEqW$1j=P@L z*ndO!fwPeq_7J_H1Sx>#L$EO_;MfYj{lKuD8ZrUtgQLUUEhvaXA$)-<61v`C=qUhI zioV&KR#l50fn!-2VT`aMv|LycLOFPT{rRSRGTBMc)A`Cl%K&4KIgMf}G%Qpb2@cB* zw8obt-BI3q8Lab!O<#zeaz{P-lI2l`2@qrjD+Qy)^VKks5&SeT(I)i?&Kf59{F`Rw zuh7Q>SQNwqLO%cu2lzcJ7eR*3!g}U)9=EQ}js-q{d%h!wl6X3%H0Z2^8f&^H;yqti4z6TNWc& zDUU8YV(ZHA*34HHaj#C43PFZq7a>=PMmj4+?C4&l=Y-W1D#1VYvJ1~K%$&g-o*-heAgLXXIGRhU zufonwl1R<@Kc8dPKkb`i5P9VFT_NOiRA=#tM0WX2Zut)_ zLjAlJS1&nnrL8x8!o$G+*z|kmgv4DMjvfnvH)7s$X=-nQC3(eU!ioQwIkaXrl+58 z@v)uj$7>i`^#+Xu%21!F#AuX|6lD-uelN9ggShOX&ZIN+G#y5T0q+RL*(T(EP)(nP744-ML= z+Rs3|2`L4I;b=WHwvKX_AD56GU+z92_Q9D*P|HjPYa$yW0o|NO{>4B1Uvq!T;g_N- zAbNf%J0QBo1cL@iahigvWJ9~A4-glDJEK?>9*+GI6)I~UIWi>7ybj#%Po}yT6d6Li z^AGh(W{NJwz#a~Qs!IvGKjqYir%cY1+8(5lFgGvl(nhFHc7H2^A(P}yeOa_;%+bh` zcql{#E$kdu?yhRNS$iE@F8!9E5NISAlyeuOhRD)&xMf0gz^J927u5aK|P- z>B%*9vSHy?L_q)OD>4+P;^tz4T>d(rqGI7Qp@@@EQ-v9w-;n;7N05{)V4c7}&Y^!`kH3}Q z4RtMV6gAARY~y$hG7uSbU|4hRMn97Dv0$Le@1jDIq&DKy{D$FOjqw{NruxivljBGw zP4iM(4Nrz^^~;{QBD7TVrb6PB=B$<-e9!0QeE8lcZLdDeb?Gv$ePllO2jgy&FSbW* zSDjDUV^=`S(Oo0;k(Idvzh}aXkfO)F6AqB?wWqYJw-1wOn5!{-ghaHb^v|B^92LmQ9QZj zHA&X)fd%B$^+TQaM@FPXM$$DdW|Vl)4bM-#?Slb^qUX1`$Yh6Lhc4>9J$I4ba->f3 z9CeGO>T!W3w(){M{OJ+?9!MK68KovK#k9TSX#R?++W4A+N>W8nnk**6AB)e;rev=$ zN_+(?(YEX;vsZ{EkEGw%J#iJYgR8A}p+iW;c@V>Z1&K->wI>!x-+!0*pn|{f=XA7J zfjw88LeeJgs4YI?&dHkBL|PRX`ULOIZlnniTUgo-k`2O2RXx4FC76;K^|ZC6WOAEw zz~V0bZ29xe=!#Xk?*b{sjw+^8l0Koy+e7HjWXgmPa4sITz+$VP!YlJ$eyfi3^6gGx6jZLpbUzX;!Z6K}aoc!1CRi zB6Lhwt%-GMcUW;Yiy6Y7hX(2oksbsi;Z6k*=;y;1!taBcCNBXkhuVPTi+1N*z*}bf z`R=&hH*Ck5oWz>FR~>MO$3dbDSJ!y|wrff-H$y(5KadrA_PR|rR>jS=*9&J*ykWLr z-1Z^QOxE=!6I z%Bozo)mW7#2Hd$-`hzg=F@6*cNz^$#BbGlIf${ZV1ADc}sNl=B72g`41|F7JtZ^BT z+y}nqn3Ug`2scS_{MjykPW2~*k$i6PhvvxJCW;n!SK5B8Rpm41fCEdy=ea-4F`rN5 zF>ClKp#4?}pI7eR#6U|}t`DA!GQJB7nT$HVV*{qPjIRU1Ou3W;I^pCt54o|ZHvWaH zooFx9L%#yv)!P;^er5LCU$5@qXMhJ-*T5Ah8|}byGNU5oMp3V)yR;hWJKojJEregX z<1UPt%&~=5OuP(|B{ty);vLdoe7o^?`tkQa7zoXKAW6D@lc+FTzucotaOfJ!(Bm zHE8f8j@6||lH`y2<&hP}Q1wr(=6ze0D6NRL{7QaE1=nTAzqjIeD}Be&@#_d*dyurz z&L7xo-D9!dS`i>^GaIPArR@r=N#-ppIh!UBcb!N*?nLUO+*%C>_dCF1IH)q>5oT(t zjQo{AoDB;mWL;3&;vTt?;bvJSj>^Gq4Jrh}S}D>G)+b!>oRDWI?c_d77$kF5ms{Gx zak*>~*5AvaB-Xl)IgdZ^Cupv6HxQ0 zM(KPaDpPsPOd)e)aFw}|=tfzg@J1P8oJx2ZBY=g4>_G(Hkgld(u&~jN((eJ}5@b1} zI(P7j443AZj*I@%q!$JQ2?DZV47U!|Tt6_;tlb`mSP3 z74DE4#|1FMDqwYbT4P6#wSI%s?*wDc>)MR$4z9ZtJg04+CTUds>1JSDwI}=vpRoRR zLqx(Tvf34CvkTMOPkoH~$CG~fSZb;(2S4Q6Vpe9G83V={hwQ>acu+MCX)@0i>Vd`% z4I8Ye+7&Kcbh(*bN1etKmrpN)v|=eI+$oD=zzii6nP&w|kn2Y-f!(v<aE zKmOz#{6PZB(8zD={il`RO6D}v(@mN_66KXUAEefgg|;VmBfP?UrfB$&zaRw7oanna zkNmVGz4Vhd!vZSnp1(&_5^t;eSv6O771BloJAHi=Pnn+aa6y(e2iiE97uZ{evzQ^8 z*lN@ZYx<-hLXP^IuYLGf<01O*>nDp0fo;;Iyt`JADrxt7-jEF(vv_btyp6CT8=@5t zm`I0lW+2+_xj2CRL|40kcYysuyYeiGihGe&a)yilqP}5h+^)m8$=mzrUe`$(?BIY> zfF7-V10Gu0CkWF)wz04&hhI>es0NS7d`cnT`4y8K!wUAKv$H09fa>KeNQvwUNDT1zn}_*RHykC$CD%*h7vRCQ&Z z4&N-!L>(@8i?K$l5)13n0%VPPV`iG7Q$2{1T3JypLSvN%1kX73goBIOEmg=Uf$9e? zm}g>JFu}EQKH>|K!)m9teoCmTc`y2Ll}msZYyy0Pkqjeid66>DP_?C{KCw94lHvLW z-+X!2YSm70s833lH0o+|A%Xwsw`@8lE3ia0n_Dve;LC7@I+i~@%$lD|3fNf&R6ob6 z@iGfx^OC4s`$|vO!0jTWwVpX;X^EqJF{i324I>N=f@u+rTN+xJGGR0LsCQc;iFD=F zbZJrgOpS;04o^wP7HF5QBaJ$KJgS2V4u02ViWD=6+7rcu`uc&MOoyf%ZBU|gQZkUg z<}ax>*Fo?d*77Ia)+{(`X45{a8>Bi$u-0BWSteyp#GJnTs?&k&<0NeHA$Qb3;SAJK zl}H*~eyD-0qHI3SEcn`_7d zq@YRsFdBig+k490BZSQwW)j}~GvM7x>2ymO4zakaHZ!q6C2{fz^NvvD8+e%7?BQBH z-}%B{oROo2+|6g%#+XmyyIJrK_(uEbg%MHlBn3^!&hWi+9c0iqM69enep#5FvV_^r z?Yr(k*5FbG{==#CGI1zU0Wk{V?UGhBBfv9HP9A-AmcJmL^f4S zY3E2$WQa&n#WRQ5DOqty_Pu z-NWQGCR^Hnu^Vo2rm`-M>zzf|uMCUd1X0{wISJL2Pp=AO5 zF@(50!g|SYw3n<_VP0T~`WUjtY**6Npphr5bD%i3#*p7h8$#;XTLJAt5J-x~O1~`z z`2C~P4%XSI(JbrEmVMEwqdsa^aqXWg;A6KBn^jDxTl!}Q!^WhprL$kb(Iqq zUS`i$tIPs#hdE-zAaMGoxcG?Z;RO2L0Y|gcjV_)FFo|e)MtTl`msLTwq>po$`H6_U zhdWK97~M>idl9GE_WgobQkK_P85H_0jN?s3O)+m&68B`_;FnbZ3W*Qm++ghSs7|T4b7m~VVV%j0gl`Iw!?+-9#Lsb!j3O%fSTVuK z37V>qM81D+Atl};23`TqEAfEkQDpz$-1$e__>X2jN>xh@Sq)I6sj@< ziJ^66GSmW9c%F7eu6&_t$UaLXF4KweZecS1ZiHPWy-$e_7`jVk74OS*!z=l#(CQ^K zW-ke|g^&0o=hn+4uh-8lUh0>!VIXXnQXwKr>`94+2~<;+`k z$|}QZ>#pm2g}8k*;)`@EnM~ZQtci%_$ink9t6`HP{gn}P1==;WDAld3JX?k%^GcTU za>m|CH|UsyFhyJBwG5=`6562hkVRMQ=_ron-Vlm$4bG^GFz|Jh5mM{J1`!!hAr~8F^w> z^YhQ=c|bFn_6~9X$v(30v$5IX;#Nl-XXRPgs{g_~RS*znH^6Vhe}8>T?aMA|qfnWO zQpf(wr^PfygfM+m2u!9}F|frrZPBQ!dh(varsYo!tCV)WA(Wn^_t=WR_G7cQU`AGx zrK^B6<}9+$w;$vra)QWMKf_Tnqg93AMVZ6Qd=q6rdB{;ZhsoT zWy9QhnpEnc@Dauz4!8gq zqDanAX#$^vf-4~ZqUJtSe?SO+Hmb?)l2#}v(8}2+P{ZZuhlib0$3G0|a5?JR>QgUUP$HTE5hb`h>imq#7P+Y*-UVLm@9km|V# zoigziFt$bxgQMwqKKhd!c--&ciywIED>faY3zHLrA{V#IA)!mq!FXxf?1coGK~N(b zjwu*@2B1^(bzFVBJO`4EJ$=it!a0kbgUvPL;Er(0io{W4G7Bkqh)=g)uS|l0YfD}f zaCJwY7vR-D=P9M68`cmtmQ^!F-$lt@0S|9G7cHgT13A0xMv)HmH#Z<4{~iYo_VOD{ z5!kU+>mUOvHouw+-y?*cNlUlDwD#;6ZvAIc$YcwG&qKZFh>EtM(Eda+w)E$HcfZyB zG*$<*ae_ApE%gxWx%O^~XMnRSNLv!y`g99F(J_m)spJAc95P|_joOIoru%atbw z9PYgkcE*8x#)-W{>96KDl&74iW<#wrK)1s zxzU{`rW5af+dT6Z@_1dG<}CtDMT`EGVEXSL_5D9)Z;6UJe-TW7)M?bY%E;8G?Yc!$ zic;F5=#dba^P~7f#qvC}Nd#XEo2r_UlgfR_`B2^W0QjXU?RAi$>f&{G_Lu8Fp0qDp z?vAdm%z#3kcZmaJ@afooB=A@>8_N~O9Yzu=ZCEikM>UgU+{%>pPvmSNzGk@*jnc5~ z(Z#H4OL^gw>)gqZ!9X|3i4LAdp9vo)?F9QCR3##{BHoZ73Uk^Ha={2rc*TBijfKH- z=$cZQdc<5%*$kVo|{+bL3 zEoU&tq*YPR)^y-SISeQNQ)YZ9v>Hm4O=J)lf(y=Yu1ao&zj#5GVGxyj%V%vl9}dw< zO;@NRd4qe@Et}E@Q;SChBR2QPKll1{*5*jT*<$$5TywvC77vt=1=0xZ46>_17YzbiBoDffH(1_qFP7v2SVhZmA_7JDB50t#C39 z8V<9(E?bVWI<7d6MzcS^w!XmZ**{AO!~DZNU)pgr=yY1 zT@!AapE;yg&hmj*g{I3vd## zx+d%^O?d%%?Dba|l~X6ZOW|>FPsrjPjn-h4swysH!RNJUWofC?K(^0uHrBPrH5#W> zMn8^@USzjUucqo%+5&))Dnnw`5l1mp>roaA99Nkk4keZl2wAF7oa(!x?@8uGWzc5Q zM}g`}zf-D@B6lVFYWmmJ8a+_%z8g$C7Ww~PD9&jki08NY!b!fK288R;E?e3Z+Pk{is%HxQU`xu9+y5 zq?DWJD7kKp(B2J$t5Ij8-)?g!T9_n<&0L8F5-D0dp>9!Qnl#E{eDtkNo#lw6rMJG$ z9Gz_Z&a_6ie?;F1Y^6I$Mg9_sml@-z6t!YLr=ml<6{^U~UIbZUUa_zy>fBtR3Rpig zc1kLSJj!rEJILzL^uE1mQ}hjMCkA|ZlWVC9T-#=~ip%McP%6QscEGlYLuUxDUC=aX zCK@}@!_@~@z;70I+Hp5#Tq4h#d4r!$Np1KhXkAGlY$ap7IZ9DY})&(xoTyle8^dBXbQUhPE6ehWHrfMh&0=d<)E2+pxvWo=@`^ zIk@;-$}a4zJmK;rnaC)^a1_a_ie7OE*|hYEq1<6EG>r}!XI9+(j>oe!fVBG%7d}?U z#ja?T@`XO(;q~fe2CfFm-g8FbVD;O7y9c;J)k0>#q7z-%oMy4l+ zW>V~Y?s`NoXkBeHlXg&u*8B7)B%alfYcCriYwFQWeZ6Qre!4timF`d$=YN~_fPM5Kc8P;B-WIDrg^-j=|{Szq6(TC)oa!V7y zLmMFN1&0lM`+TC$7}on;!51{d^&M`UW ztI$U4S&}_R?G;2sI)g4)uS-t}sbnRoXVwM!&vi3GfYsU?fSI5Hn2GCOJ5IpPZ%Y#+ z=l@;;{XiY_r#^RJSr?s1) z4b@ve?p5(@YTD-<%79-%w)Iv@!Nf+6F4F1`&t~S{b4!B3fl-!~58a~Uj~d4-xRt`k zsmGHs$D~Wr&+DWK$cy07NH@_z(Ku8gdSN989efXqpreBSw$I%17RdxoE<5C^N&9sk!s2b9*#}#v@O@Hgm z2|U7Gs*@hu1JO$H(Mk)%buh~*>paY&Z|_AKf-?cz6jlT-v6 zF>l9?C6EBRpV2&c1~{1$VeSA|G7T(VqyzZr&G>vm87oBq2S%H0D+RbZm}Z`t5Hf$C zFn7X*;R_D^ z#Ug0tYczRP$s!6w<27;5Mw0QT3uNO5xY($|*-DoR1cq8H9l}_^O(=g5jLnbU5*SLx zGpjfy(NPyjL`^Oln_$uI6(aEh(iS4G=$%0;n39C(iw79RlXG>W&8;R1h;oVaODw2nw^v{~`j(1K8$ z5pHKrj2wJhMfw0Sos}kyOS48Dw_~=ka$0ZPb!9=_FhfOx9NpMxd80!a-$dKOmOGDW zi$G74Sd(-u8c!%35lL|GkyxZdlYUCML{V-Ovq{g}SXea9t`pYM^ioot&1_(85oVZ6 zUhCw#HkfCg7mRT3|>99{swr3FlA@_$RnE?714^o;vps4j4}u=PfUAd zMmV3j;Rogci^f!ms$Z;gqiy7>soQwo7clLNJ4=JAyrz;=*Yhe8q7*$Du970BXW89Xyq92M4GSkNS-6uVN~Y4r7iG>{OyW=R?@DmRoi9GS^QtbP zFy2DB`|uZTv8|ow|Jcz6?C=10U$*_l2oWiacRwyoLafS!EO%Lv8N-*U8V+2<_~eEA zgPG-klSM19k%(%;3YM|>F||hE4>7GMA(GaOvZBrE{$t|Hvg(C2^PEsi4+)w#P4jE2XDi2SBm1?6NiSkOp-IT<|r}L9)4tLI_KJ*GKhv16IV}An+Jyx z=Mk`vCXkt-qg|ah5=GD;g5gZQugsv!#)$@ zkE=6=6W9u9VWiGjr|MgyF<&XcKX&S3oN{c{jt-*1HHaQgY({yjZiWW97rha^TxZy< z2%-5X;0EBP>(Y9|x*603*Pz-eMF5*#4M;F`QjTBH>rrO$r3iz5 z?_nHysyjnizhZQMXo1gz7b{p`yZ8Q78^ zFJ3&CzM9fzAqb6ac}@00d*zjW`)TBzL=s$M`X*0{z8$pkd2@#4CGyKEhzqQR!7*Lo@mhw`yNEE6~+nF3p;Qp;x#-C)N5qQD)z#rmZ#)g*~Nk z)#HPdF_V$0wlJ4f3HFy&fTB#7Iq|HwGdd#P3k=p3dcpfCfn$O)C7;y;;J4Za_;+DEH%|8nKwnWcD zBgHX)JrDRqtn(hC+?fV5QVpv1^3=t2!q~AVwMBXohuW@6p`!h>>C58%sth4+Baw|u zh&>N1`t(FHKv(P+@nT$Mvcl){&d%Y5dx|&jkUxjpUO3ii1*^l$zCE*>59`AvAja%`Bfry-`?(Oo?5wY|b4YM0lC?*o7_G$QC~QwKslQTWac z#;%`sWIt8-mVa1|2KH=u!^ukn-3xyQcm4@|+Ra&~nNBi0F81BZT$XgH@$2h2wk2W% znpo1OZuQ1N>bX52II+lsnQ`WVUxmZ?4fR_f0243_m`mbc3`?iy*HBJI)p2 z`GQ{`uS;@;e1COn-vgE2D!>EheLBCF-+ok-x5X8Cu>4H}98dH^O(VlqQwE>jlLcs> zNG`aSgDNHnH8zWw?h!tye^aN|%>@k;h`Z_H6*py3hHO^6PE1-GSbkhG%wg;+vVo&dc)3~9&` zPtZtJyCqCdrFUIEt%Gs_?J``ycD16pKm^bZn>4xq3i>9{b`Ri6yH|K>kfC; zI5l&P)4NHPR)*R0DUcyB4!|2cir(Y1&Bsn3X8v4D(#QW8Dtv@D)CCO zadQC85Zy=Rkrhm9&csynbm>B_nwMTFah9ETdNcLU@J{haekA|9*DA2pY&A|FS*L!*O+>@Q$00FeL+2lg2NWLITxH5 z0l;yj=vQWI@q~jVn~+5MG!mV@Y`gE958tV#UcO#56hn>b69 zM;lq+P@MW=cIvIXkQmKS$*7l|}AW%6zETA2b`qD*cL z(=k4-4=t6FzQo#uMXVwF{4HvE%%tGbiOlO)Q3Y6D<5W$ z9pm>%TBUI99MC`N9S$crpOCr4sWJHP)$Zg#NXa~j?WeVo03P3}_w%##A@F|Bjo-nNxJZX%lbcyQtG8sO zWKHes>38e-!hu1$6VvY+W-z?<942r=i&i<88UGWdQHuMQjWC-rs$7xE<_-PNgC z_aIqBfG^4puRkogKc%I-rLIVF=M8jCh?C4!M|Q=_kO&3gwwjv$ay{FUDs?k7xr%jD zHreor1+#e1_;6|2wGPtz$``x}nzWQFj8V&Wm8Tu#oaqM<$BLh+Xis=Tt+bzEpC}w) z_c&qJ6u&eWHDb<>p;%F_>|`0p6kXYpw0B_3sIT@!=fWHH`M{FYdkF}*CxT|`v%pvx z#F#^4tdS0|O9M1#db%MF(5Opy;i( zL(Pc2aM4*f_Bme@o{xMrsO=)&>YKQw+)P-`FwEHR4vjU>#9~X7ElQ#sRMjR^Cd)wl zg^67Bgn9CK=WP%Ar>T4J!}DcLDe z=ehSmTp##KyQ78cmArL=IjOD6+n@jHCbOatm)#4l$t5YV?q-J86T&;>lEyK&9(XLh zr{kPuX+P8LN%rd%8&&Ia)iKX_%=j`Mr*)c)cO1`-B$XBvoT3yQCDKA>8F0KL$GpHL zPe?6dkE&T+VX=uJOjXyrq$BQ`a8H@wN1%0nw4qBI$2zBx)ID^6;Ux+? zu{?X$_1hoz9d^jkDJpT-N6+HDNo%^MQ2~yqsSBJj4@5;|1@w+BE04#@Jo4I63<~?O?ok%g%vQakTJKpMsk&oeVES1>cnaF7ZkFpqN6lx` zzD+YhR%wq2DP0fJCNC}CXK`g{AA6*}!O}%#0!Tdho4ooh&a5&{xtcFmjO4%Kj$f(1 zTk||{u|*?tAT{{<)?PmD_$JVA;dw;UF+x~|!q-EE*Oy?gFIlB*^``@ob2VL?rogtP z0M34@?2$;}n;^OAV2?o|zHg`+@Adk+&@Syd!rS zWvW$e5w{onua4sp+jHuJ&olMz#V53Z5y-FkcJDz>Wk%_J>COk5<0ya*aZLZl9LH}A zJhJ`Q-n9K+c8=0`FWE^x^xn4Fa7PDUc;v2+us(dSaoIUR4D#QQh91R!${|j{)=Zy1 zG;hqgdhSklM-VKL6HNC3&B(p1B)2Nshe7)F=-HBe=8o%OhK1MN*Gq6dBuPvqDRVJ{ z;zVNY?wSB%W0s^OMR_HL(Ws)va7eWGF*MWx<1wG7hZ}o=B62D?i|&0b14_7UG287YDr%?aYMMpeCkY1i`b+H!J9sqrvKc#Y6c8At@QiLSwj)@ifz~Z|c$lOMA@?cPqFRmZ%_>bz2X4(B=`^3;MDjsEeAO=? zSoD&+L>A|fGt7+6kF2@LqhL06sD%|~YsIe=EcWqy{e_61N_D(*CacnMvyXMjP87HI z4PT6!$fzxx{}=>jeqzkkoN+!r9e|@lZUN4pn(T28v`k=_vIhTn^i9O3qTqd)-%!QQ zYB6*6B@&b(!#X4C~59SLZuorNU_wWZA36{>O%iX)VS5NNZh49C_ppI>?)wwml}_0MLzOXT>lmo#&Ew6d?mu8~~I_^4VGBQtCAke;RQa5DL` z1PFDPsKb3CS$v;RhlQ1J@AHa1VRuuxp}NOIvrC>4$$A0Ix0VpAc0lfG%8{mR{TRQ( zbXM#1Tci3H*Wt>cVuMta^6^z`=^B@j+YhJqq9?>zZPxyg2U(wvod=uwJs{8gtpyab zXHQX<0FOGW6+dw&%c_qMUOI^+Rnb?&HB7Fee|33p4#8i>%_ev(aTm7N1f#6lV%28O zQ`tQh$VDjy8x(Lh#$rg1Kco$Bw%gULq+lc4$&HFGvLMO30QBSDvZ#*~hEHVZ`5=Kw z3y^9D512@P%d~s{x!lrHeL4!TzL`9(ITC97`Cwnn8PSdxPG@0_v{No|kfu3DbtF}K zuoP+88j4dP+Bn7hlGwU$BJy+LN6g&d3HJWMAd1P9xCXG-_P)raipYg5R{KQO$j;I9 z1y1cw#13K|&kfsRZ@qQC<>j=|OC?*v1|VrY$s=2!{}e33aQcZghqc@YsHKq^)kpkg z>B;CWNX+K=u|y#N)O>n5YuyvPl5cO6B^scmG?J zC8ix)E1PlhNaw8FpD+b|D$z`Id^4)rJe78MNiBga?Z- z0$L&MRTieSB1_E#KaN*H#Ns1}?zOA%Ybr{G+Sn3moXTVZj=L`nt?D&-MjOMz-Yq&@ z$P3h23d_F8Dcf*?txX7}p>nM*s+65t z1il8bHHsBynUK|aEXSjzY6sz1nZ%|%XeWTcGLRyRl@q4YAR)JovbdTTY&7u>@}28A zgV^Npp?}I!?3K7IXu9ml-Lw;w@9m zBYTeU+Seh8uJ-w?4e_6byq0f7>O3xm(hO}Y=fgU5^vW|>0yQ^0+?}LT55ei$i zzlU-iRbd8TRX9Ept%h%ariV=%u%F@@FA>U*XdAalcH%>#5_a&w)g`uW%3}m?vP- zc5}DkuF6ruKDwEYj+2YTSQ9=rkp19U5P@(zRm(nLod(sG9{~nw1BUoS2OFDXa{xfw zZ~UaZLFUZxfQ*9?_X?*~`d;nn-BbaefLJ`DT13KF6?T5Mnt;v5d>H}s)aAIzJcs#B z|CuXPJKww}hWBKsUfks#Kh$)ptp?5U1b@ttXFRbe_BZ&_R9XC6CA4WhWhMUE9Y2H4 z{w#CBCR<)Fd1M;mx*m?Z=L-^1kv1WKtqG(BjMiR4M^5yN4rlFM6oGUS2Wf~7Z@e*- ze84Vr`Bmi!(a1y}-m^HHMpbAiKPVEv|(7=|}D#Ihfk+-S5Hlkfch02z&$(zS3vrYz2g*ic{xBy~*gIp(eG}^gMc7 zPu2Eivnp@BH3SOgx!aJXttx*()!=2)%Bf$Gs^4cCs@)=(PJNxhH5lVY&qSZYaa?A^LhZW`B9(N?fx<^gCb(VE%3QpA*_Pohgp6vCB36iVaq zc1TI%L2Le?kuv?6Dq`H+W>AqnjyEzUBK948|DB|)U0_4DzWF#7L{agwo%y$hC>->r z4|_g_6ZC!n2=GF4RqVh6$$reQ(bG0K)i9(oC1t6kY)R@DNxicxGxejwL2sB<>l#w4 zE$QkyFI^(kZ#eE5srv*JDRIqRp2Totc8I%{jWhC$GrPWVc&gE1(8#?k!xDEQ)Tu~e zdU@aD8enALmN@%1FmWUz;4p}41)@c>Fg}1vv~q>xD}KC#sF|L&FU);^Ye|Q;1#^ps z)WmmdQI2;%?S%6i86-GD88>r|(nJackvJ#50vG6fm$1GWf*f6>oBiDKG0Kkwb17KPnS%7CKb zB7$V58cTd8x*NXg=uEX8Man_cDu;)4+P}BuCvYH6P|`x-#CMOp;%u$e z&BZNHgXz-KlbLp;j)si^~BI{!yNLWs5fK+!##G;yVWq|<>7TlosfaWN-;C@oag~V`3rZM_HN`kpF`u1p# ztNTl4`j*Lf>>3NIoiu{ZrM9&E5H~ozq-Qz@Lkbp-xdm>FbHQ2KCc8WD7kt?=R*kG# z!rQ178&ZoU(~U<;lsg@n216Ze3rB2FwqjbZ=u|J?nN%<4J9(Bl(90xevE|7ejUYm9 zg@E_xX}u2d%O1mpA2XzjRwWinvSeg)gHABeMH(2!A^g@~4l%8e0WWAkBvv60Cr>TR zQB1%EQ zUoZeUdqjh+1gFo6h~C~z#A57mf5ibmq$y_uVtA_kWv8X)CzfVEooDaY!#P?5$Y zGPKXbE<75nc%D-|w4OrP#;87oL@2^4+sxKah;a-5&z_&SUf~-z(1}bP=tM^GYtR3a z!x4zjSa^)KWG6jxfUI#{<26g$iAI;o_+B{LXY@WfWEdEl6%#8s3@b`?&Tm#aSK!~| z^%DdrXnijW`d!ajWuKApw&{L+WCPpFialo&^dZ9jC7A%BO`2ZF&YUDe;Yu|zFuv`2 z)BE*7Lkay)M7uohJ)446X``0x0%PzPTWY92`1Oq4a2D_7V0wypPnXFR)WM0IlFgg@ zqz#hv2xJEQL8eu}O;e(w4rSA?5|eZHbS6jENytJBq59?bOf>Wrl8ySZH36H(6fGR#vHM6q zn}!7!I@4$*+LFXs{x?|=q2*QtYT%Lw3+5(8uc0j8o3}TrG(zSV#>4wo6~)u|R+Yx# z?0$AspZDjv{dfv417~C17Oy%Fal{%+B6H(NX`$Bl>II-L3N3 zZc+sKZbqewU*&_Xt;9k=%4*aVYBvE1n&JZS7Uqjd%n8nOQmzh^x#vWK{;In~=QO)g zT-n3OU(1@3QfL|$g1d2xeBb@O15Rl01+hmpup2De7p%Yrd$E7(In!*R+;IJZh}v!svi z;7N~pq8KZDXXap0qd_D=Y^B)rz4S0^SF=&v6YYTAV$ad43#x!+n~-6< zK{8*vWoAdW(gGGt&URD}@g6tMoY(+Lw=vvxhfIIK9AjvNF_(W}1Rxn(mp;tJfDV<0 zbJN0t(@Xb8UeO{&T{$$uDrs7)j$}=?WsuDl+T2N5Y<4TMHGOMcocPr$%~(yvtKv(n z`U96d!D0cb9>Dx2zz$m&lAhazs%UeR^K*gb>d8CPs+?qlpfA;t{InXa)^2ryC(FU(Zc6Xbnnh`lg`K&g^JeS>}^c0MJKUCfV+~ zV(EN0Z5ztoN;hqcj!8V+VRbSltJ<~|y`U+9#wv|~H zNE!j9uXa=dec@JQSgJ6N6@Il&tzCBJv9#ldR`Lm*<)YwH4tdlAlG0Fl8Nfa(J~c%DQ2AA-}x8D=p(l#n1+hgx;N;1Aq?lq@{Lt9FKu89CjnnHD1G_@p;%Lp`+b@ttb33!E_Xt;QUD9~nRQl&xAro9-{+&6^ljK2f-d>&qy&d#0xwH z@slNv@ULKp!Cf*JHuS@#4c?F->WjPc)yiuSargAIEg>muRxzY?Hzdq@G5CS)U1*Et zE2SLh=@DI1J(guiy2Igq(?(xI9WL%g^f@{5Hmr|!Qz4`vn|LjrtO=b~I6~5EU5Fxy z;-#<)6w#w=DkpSthAu+E;OL?!?6C9Mwt*o(@68(Jhvs-eX4V z=d=>HI|`3J%H5X|gSrC8KH^IL?h5=3ID6svwHH@(wRbSG`Zsor^q4`3PCn#-(YX?< z_q8+T)51$E0xyKR{L!LN(G=+9K6$3#PDT^IAe|Igkx=!4#rqKWoXiZdh`&ocjp=Ok zemJe6*{it~>;sr(B0fSmp(S#*y5I0)OOz~Oe6Im+($S}e3tyx7Y6pA8vKCBmSEQDa zLfkm*;uMbTLpcR0)tF_v-lbK%`5>POyI2E(!)2=Rj0p;WKi=|UNt6HsQv0xR3QIK9 zsew(AFyzH!7Azxum{%VC^`cqhGdGbABGQ4cYdNBPTx+XpJ=NUEDeP^e^w^AOE1pQI zP{Us-sk!v$gj}@684E!uWjzvpoF|%v-6hwnitN1sCSg@(>RDCVgU8Ile_-xX`hL6u zzI4*Q)AVu(-ef8{#~P9STQ5t|qIMRoh&S?7Oq+cL6vxG?{NUr@k(~7^%w)P6nPbDa~4Jw}*p-|cT4p1?)!c0FoB(^DNJ+FDg+LoP6=RgB7Or673WD5MG&C!4< zerd6q$ODkBvFoy*%cpHGKSt z3uDC6Sc=xvv@kDzRD)aIO`x}BaWLycA%(w-D`Pd+uL*rL|etagQ;U&xt_9?7#}=}5HI)cU-0 z%pMA`>Xb7s)|Y)4HKSZOu;{lg=KjeIyXb0{@EM`FTDkLRH`!W%z*lQJ74P%Ka76)H zblrSIzf+dMWbO`g;=(b@{pS)zUcO&GrIFe%&?YeX4r8B2bBArB%-5ZrQ+vonr%AYy z1+u0*K{UVUmV>h5vD!F;6}a%KdMZQLs04oGkpiaC)zI( zT2U9qta5o|6Y+It1)sE8>u&0)W~l$NX@ZQ8UZfB=`($EW6?FT%{EoRhOrb9)z@3r8y?Z99FNLDE;7V=Q zotj&igu*Rh^VQn3MQKBq!T{yTwGhn1YL6k*?j?{_ek5xe8#i#GG4S-a_Re2lssG!} z`Y-d0BcOdB@!m?4y&hMN68}#0-IIlm_xO)d#}ugX{q^OZe{-@LeJyv`cY&ze4t2~! zKb{qX-j;kt{?gC(vW%}X4pm@1F?~LH{^Q8d@X$dy@5ff~p!J3zmA>H`A)y+6RB_h* zZfIO+bd=*LiymRw{asW%xxaVl33_xtdVrrqIPn zc@y8oMJvNtgcO~4i0`f)GCFkWY8EF?4duLVjHTdb6oYLnO9}Q-pe{CKQJL)hV8)JI z$mVA0Dq&7Z1TbYdSC(WbJ+IBjXngZTu&I+vHF|>Zo$757{8lL;8Zr-Exkf?3jzN5k z_d9I>{>^J?!l)< zNd$7E9FVrta}3qy3L7Ys$^fRWNuu^hs^{*eXvazd&+Q*?lTfc>2+EdP(o0P_Z05HX zVKsfFAQ{t^CRu~Dw(CuJ>tvx*p$5@flA>QRl455b&{*U?xU8`)nF2T$uu_(l8VNtq z?pBiRQIckGzk8W&SFSB=g6eG`ZC;6v9w`?eF*S}3E@N`2ropeHP)E}o?qJkyVEI;K$!)bWY zt9>4WmDVJh7U~m$|K`T#hF!v|znj^=M;69uXrFys#51XT;DbMr4H)>7UQ1e2(cuQf z4kr~Tt1tpBB2GaJ(|j~lHgW40EgMMVqR6eJoJig1SBg|2=$~4I3P0eP$q%_`sS&4~ z26=&a&tLjQbch1`cVXa-2fTl1y8}->|Nqu?uVrNTov!=VKh)g89wUPTgAzkSKZ57_ zr=B^mcldE3K04t4{;RaG53&9yovq;@aR#VHx+R1^^*kr-vEEd!uea68Z<{R%_DD6fn&T4 zu;fDj07L-(_fLSJGdkeh&c&7A(ZLj`7iwnkAcqUexU;WjUkqeg1m1-IUZTIZA(4dtr2Gr`e{BIejlCgS<33MB=1!8?a74!F%=Uo7N`F@k} ze+1C_eU4Y_$mvdjci zwEtCIphA2PBzBhng5=M#e4r%)RW5rVD|_`PvY$7BK`}w~d>%0O9sY#*LUAq=^OjMF^PY5m<7!=s5jyRfosCQAo#hL`h5vN-M}6Q z0Li}){5?wi8)GVHNkF|U9*8V5ej)nhb^TLw1KqiPK(@{P1^L&P=`ZNt?_+}&0(8Uh zfyyZFPgMV7ECt;Jdw|`|{}b$w4&x77VxR>8wUs|GQ5FBf1UlvasqX$qfk5rI4>Wfr zztH>y`=daAef**C12yJ7;LDf&3;h3X+5@dGPy@vS(RSs3CWimbTp=g \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/ump/integration_test/gradlew.bat b/ump/integration_test/gradlew.bat new file mode 100644 index 0000000000..51923e981d --- /dev/null +++ b/ump/integration_test/gradlew.bat @@ -0,0 +1,104 @@ +@rem Copyright 2021 Google LLC +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ump/integration_test/integration_test.xcodeproj/project.pbxproj b/ump/integration_test/integration_test.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..3f0c3ff6ad --- /dev/null +++ b/ump/integration_test/integration_test.xcodeproj/project.pbxproj @@ -0,0 +1,383 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D61C5F8C22BABA9B00A79141 /* Images.xcassets */; }; + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D61C5F9222BABAD100A79141 /* integration_test.cc */; }; + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D62CCBBF22F367140099BE9F /* gmock-all.cc */; }; + D640F3172819C85800AC956E /* empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D640F3162819C85800AC956E /* empty.swift */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D67D355622BABD2100292C1D /* gtest-all.cc */; }; + D686A3292A8B16F20034845A /* AppTrackingTransparency.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */; }; + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E722CB322900C2651A /* ios_app_framework.mm */; }; + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */ = {isa = PBXBuildFile; fileRef = D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */; }; + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EC22CB323300C2651A /* firebase_test_framework.cc */; }; + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */ = {isa = PBXBuildFile; fileRef = D6C179EF22CB32A000C2651A /* app_framework.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* integration_test.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = integration_test.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 529226D51C85F68000C89379 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 529226D71C85F68000C89379 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 529226D91C85F68000C89379 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 529226EE1C85F68000C89379 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + D61C5F8C22BABA9B00A79141 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + D61C5F8D22BABA9C00A79141 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D61C5F9222BABAD100A79141 /* integration_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = integration_test.cc; path = src/integration_test.cc; sourceTree = ""; }; + D62CCBBF22F367140099BE9F /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "external/googletest/src/googlemock/src/gmock-all.cc"; sourceTree = ""; }; + D62CCBC122F367320099BE9F /* gmock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gmock.h; path = external/googletest/src/googlemock/include/gmock/gmock.h; sourceTree = ""; }; + D640F3162819C85800AC956E /* empty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = empty.swift; path = src/empty.swift; sourceTree = ""; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D67D355622BABD2100292C1D /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "external/googletest/src/googletest/src/gtest-all.cc"; sourceTree = ""; }; + D67D355722BABD2100292C1D /* gtest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = gtest.h; path = external/googletest/src/googletest/include/gtest/gtest.h; sourceTree = ""; }; + D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; }; + D6C179E722CB322900C2651A /* ios_app_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_app_framework.mm; path = src/ios/ios_app_framework.mm; sourceTree = ""; }; + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_firebase_test_framework.mm; path = src/ios/ios_firebase_test_framework.mm; sourceTree = ""; }; + D6C179EB22CB323300C2651A /* firebase_test_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = firebase_test_framework.h; path = src/firebase_test_framework.h; sourceTree = ""; }; + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = firebase_test_framework.cc; path = src/firebase_test_framework.cc; sourceTree = ""; }; + D6C179ED22CB323300C2651A /* app_framework.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_framework.h; path = src/app_framework.h; sourceTree = ""; }; + D6C179EF22CB32A000C2651A /* app_framework.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = app_framework.cc; path = src/app_framework.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + D686A3292A8B16F20034845A /* AppTrackingTransparency.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D61C5F8C22BABA9B00A79141 /* Images.xcassets */, + D61C5F8D22BABA9C00A79141 /* Info.plist */, + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* integration_test.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D686A3282A8B16F20034845A /* AppTrackingTransparency.framework */, + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + D640F3162819C85800AC956E /* empty.swift */, + D62CCBC122F367320099BE9F /* gmock.h */, + D62CCBBF22F367140099BE9F /* gmock-all.cc */, + D67D355622BABD2100292C1D /* gtest-all.cc */, + D67D355722BABD2100292C1D /* gtest.h */, + D6C179EF22CB32A000C2651A /* app_framework.cc */, + D6C179ED22CB323300C2651A /* app_framework.h */, + D6C179EC22CB323300C2651A /* firebase_test_framework.cc */, + D6C179EB22CB323300C2651A /* firebase_test_framework.h */, + D61C5F9222BABAD100A79141 /* integration_test.cc */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + D6C179E722CB322900C2651A /* ios_app_framework.mm */, + D6C179E822CB322900C2651A /* ios_firebase_test_framework.mm */, + ); + name = ios; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* integration_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = integration_test; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* integration_test.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 529226CA1C85F68000C89379 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0640; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 529226D11C85F68000C89379 = { + CreatedOnToolsVersion = 6.4; + DevelopmentTeam = EQHXZ8M8AV; + LastSwiftMigration = 1320; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* integration_test */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D61C5F8E22BABA9C00A79141 /* Images.xcassets in Resources */, + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D67D355822BABD2200292C1D /* gtest-all.cc in Sources */, + D62CCBC022F367140099BE9F /* gmock-all.cc in Sources */, + D6C179EA22CB322900C2651A /* ios_firebase_test_framework.mm in Sources */, + D61C5F9622BABAD200A79141 /* integration_test.cc in Sources */, + D6C179E922CB322900C2651A /* ios_app_framework.mm in Sources */, + D640F3172819C85800AC956E /* empty.swift in Sources */, + D6C179F022CB32A000C2651A /* app_framework.cc in Sources */, + D6C179EE22CB323300C2651A /* firebase_test_framework.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 529226F71C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 529226F81C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 529226FA1C85F68000C89379 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + EXCLUDED_ARCHS = i386; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.3; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + EXCLUDED_ARCHS = i386; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + "\"$(SRCROOT)/external/googletest/src/googletest/include\"", + "\"$(SRCROOT)/external/googletest/src/googlemock/include\"", + "\"$(SRCROOT)/external/googletest/src/googletest\"", + "\"$(SRCROOT)/external/googletest/src/googlemock\"", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.3; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "integration_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/ump/integration_test/proguard.pro b/ump/integration_test/proguard.pro new file mode 100644 index 0000000000..2d04b8a9a5 --- /dev/null +++ b/ump/integration_test/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/ump/integration_test/res/layout/main.xml b/ump/integration_test/res/layout/main.xml new file mode 100644 index 0000000000..56e8488b7a --- /dev/null +++ b/ump/integration_test/res/layout/main.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/ump/integration_test/res/values/strings.xml b/ump/integration_test/res/values/strings.xml new file mode 100644 index 0000000000..ff36817020 --- /dev/null +++ b/ump/integration_test/res/values/strings.xml @@ -0,0 +1,20 @@ + + + + Firebase GMA Integration Test + diff --git a/ump/integration_test/settings.gradle b/ump/integration_test/settings.gradle new file mode 100644 index 0000000000..7e56f6228e --- /dev/null +++ b/ump/integration_test/settings.gradle @@ -0,0 +1,41 @@ +// Copyright 2021 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. + +def firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir') +if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR') + if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) { + if ((file('../../cpp_sdk_version.json')).exists()) { + firebase_cpp_sdk_dir = file('../..').absolutePath + } + else if ((file('firebase_cpp_sdk')).exists()) { + firebase_cpp_sdk_dir = 'firebase_cpp_sdk' + } else { + throw new StopActionException( + 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' + + 'environment variable must be set to reference the Firebase C++ ' + + 'SDK install directory. This is used to configure static library ' + + 'and C/C++ include paths for the SDK.') + } + } +} +if (!(new File(firebase_cpp_sdk_dir)).exists()) { + throw new StopActionException( + sprintf('Firebase C++ SDK directory %s does not exist', + firebase_cpp_sdk_dir)) +} +gradle.ext.firebase_cpp_sdk_dir = "$firebase_cpp_sdk_dir" +includeBuild("$firebase_cpp_sdk_dir") { + name = "firebase_cpp_sdk" +} diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc new file mode 100644 index 0000000000..e26beb6ff3 --- /dev/null +++ b/ump/integration_test/src/integration_test.cc @@ -0,0 +1,3328 @@ +// Copyright 2021 Google LLC. All rights reserved. +// +// 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. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "app_framework.h" // NOLINT +#include "firebase/app.h" +#include "firebase/gma.h" +#include "firebase/gma/types.h" +#include "firebase/gma/ump.h" +#include "firebase/util.h" +#include "firebase_test_framework.h" // NOLINT + +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +// includes for phone-only tests. +#include +#include +#endif // defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + +// The TO_STRING macro is useful for command line defined strings as the quotes +// get stripped. +#define TO_STRING_EXPAND(X) #X +#define TO_STRING(X) TO_STRING_EXPAND(X) + +// Path to the Firebase config file to load. +#ifdef FIREBASE_CONFIG +#define FIREBASE_CONFIG_STRING TO_STRING(FIREBASE_CONFIG) +#else +#define FIREBASE_CONFIG_STRING "" +#endif // FIREBASE_CONFIG + +namespace firebase_testapp_automated { + +// The GMA app IDs for the test app. +#if defined(ANDROID) +// If you change the GMA app ID for your Android app, make sure to change it +// in AndroidManifest.xml as well. +const char* kGmaAppID = "ca-app-pub-3940256099942544~3347511713"; +#else +// If you change the GMA app ID for your iOS app, make sure to change the +// value for "GADApplicationIdentifier" in your Info.plist as well. +const char* kGmaAppID = "ca-app-pub-3940256099942544~1458002511"; +#endif + +// These ad units IDs have been created specifically for testing, and will +// always return test ads. +#if defined(ANDROID) +const char* kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; +const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; +const char* kRewardedAdUnit = "ca-app-pub-3940256099942544/5224354917"; +const char* kNativeAdUnit = "ca-app-pub-3940256099942544/2247696110"; +#else +const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; +const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; +const char* kRewardedAdUnit = "ca-app-pub-3940256099942544/1712485313"; +const char* kNativeAdUnit = "ca-app-pub-3940256099942544/3986624511"; +#endif + +// Used in a test to send an errant ad unit id. +const char* kBadAdUnit = "oops"; + +// Standard Banner Ad Size. +static const int kBannerWidth = 320; +static const int kBannerHeight = 50; + +enum AdCallbackEvent { + AdCallbackEventClicked = 0, + AdCallbackEventClosed, + AdCallbackEventAdImpression, + AdCallbackEventOpened, + AdCallbackEventPaidEvent +}; + +// Error domains vary across phone SDKs. +#if defined(ANDROID) +const char* kErrorDomain = "com.google.android.gms.ads"; +#else +const char* kErrorDomain = "com.google.admob"; +#endif + +// Sample test device IDs to use in making the request. +// You can replace these with actual device IDs for certain tests (e.g. UMP) +// to work on hardware devices. +const std::vector kTestDeviceIDs = { + "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; + +// Sample keywords to use in making the request. +static const std::vector kKeywords({"GMA", "C++", "Fun"}); + +// "Extra" key value pairs can be added to the request as well. Typically +// these are used when testing new features. +static const std::map kGmaAdapterExtras = { + {"the_name_of_an_extra", "the_value_for_that_extra"}, + {"heres", "a second example"}}; + +#if defined(ANDROID) +static const char* kAdNetworkExtrasClassName = + "com/google/ads/mediation/admob/AdMobAdapter"; +#else +static const char* kAdNetworkExtrasClassName = "GADExtras"; +#endif + +// Class nname of the GMA SDK returned in initialization results. +#if defined(ANDROID) +const char kGmaClassName[] = "com.google.android.gms.ads.MobileAds"; +#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE +const char kGmaClassName[] = "GADMobileAds"; +#else // desktop +const char kGmaClassName[] = "stub"; +#endif + +// Used to detect kAdErrorCodeAdNetworkClassLoadErrors when loading +// ads. +static const char* kAdNetworkExtrasInvalidClassName = "abc123321cba"; + +static const char* kContentUrl = "http://www.firebase.com"; + +static const std::vector kNeighboringContentURLs = { + "test_url1", "test_url2", "test_url3"}; + +using app_framework::LogDebug; +using app_framework::LogInfo; +using app_framework::LogWarning; +using app_framework::ProcessEvents; + +using firebase_test_framework::FirebaseTest; + +using testing::AnyOf; +using testing::Contains; +using testing::ElementsAre; +using testing::Eq; +using testing::HasSubstr; +using testing::Pair; +using testing::Property; + +class FirebaseGmaTest : public FirebaseTest { + public: + FirebaseGmaTest(); + ~FirebaseGmaTest() override; + + static void SetUpTestSuite(); + static void TearDownTestSuite(); + + void SetUp() override; + void TearDown() override; + + protected: + firebase::gma::AdRequest GetAdRequest(); + firebase::Variant GetVariantMap(); + + static firebase::App* shared_app_; +}; + +class FirebaseGmaUITest : public FirebaseGmaTest { + public: + FirebaseGmaUITest(); + ~FirebaseGmaUITest() override; + + void SetUp() override; +}; + +class FirebaseGmaMinimalTest : public FirebaseTest { + public: + FirebaseGmaMinimalTest(); + ~FirebaseGmaMinimalTest() override; +}; + +// Runs GMA Tests on methods and functions that should be run +// before GMA initializes. +class FirebaseGmaPreInitializationTests : public FirebaseGmaTest { + public: + FirebaseGmaPreInitializationTests(); + ~FirebaseGmaPreInitializationTests() override; + + static void SetUpTestSuite(); + + void SetUp() override; +}; + +firebase::App* FirebaseGmaTest::shared_app_ = nullptr; + +void PauseForVisualInspectionAndCallbacks() { + app_framework::ProcessEvents(300); +} + +void InitializeGma(firebase::App* shared_app) { + LogDebug("Initializing GMA."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(shared_app, nullptr, + [](::firebase::App* app, void* /* userdata */) { + LogDebug("Try to initialize GMA"); + firebase::InitResult result; + ::firebase::gma::Initialize(*app, &result); + return result; + }); + + FirebaseGmaTest::WaitForCompletion(initializer.InitializeLastResult(), + "Initialize"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized GMA."); +} + +void FirebaseGmaTest::SetUpTestSuite() { + LogDebug("Initialize Firebase App."); + + FindFirebaseConfig(FIREBASE_CONFIG_STRING); + +#if defined(ANDROID) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(ANDROID) + + InitializeGma(shared_app_); +} + +void FirebaseGmaTest::TearDownTestSuite() { + // GMA does some of its initialization in the main thread, so if you terminate + // it before initialization has completed, it can cause issues. So wait for + // any pending GMA initialization to be completed before calling terminate. + WaitForCompletion(firebase::gma::InitializeLastResult(), + "gma::InitializeLastResult"); + LogDebug("Shutdown GMA."); + firebase::gma::Terminate(); + LogDebug("Shutdown Firebase App."); + delete shared_app_; + shared_app_ = nullptr; +} + +FirebaseGmaTest::FirebaseGmaTest() {} + +FirebaseGmaTest::~FirebaseGmaTest() {} + +void FirebaseGmaTest::SetUp() { + TEST_DOES_NOT_REQUIRE_USER_INTERACTION; + ProcessEvents(100); // Flush main thread queue. + FirebaseTest::SetUp(); + + // This example uses ad units that are specially configured to return test ads + // for every request. When using your own ad unit IDs, however, it's important + // to register the device IDs associated with any devices that will be used to + // test the app. This ensures that regardless of the ad unit ID, those + // devices will always receive test ads in compliance with GMA policy. + // + // Device IDs can be obtained by checking the logcat or the Xcode log while + // debugging. They appear as a long string of hex characters. + firebase::gma::RequestConfiguration request_configuration; + request_configuration.test_device_ids = kTestDeviceIDs; + request_configuration.test_device_ids.push_back(GetDebugDeviceId()); + firebase::gma::SetRequestConfiguration(request_configuration); +} + +void FirebaseGmaTest::TearDown() { FirebaseTest::TearDown(); } + +firebase::gma::AdRequest FirebaseGmaTest::GetAdRequest() { + firebase::gma::AdRequest request; + + // Additional keywords to be used in targeting. + for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); + ++keyword_iter) { + request.add_keyword((*keyword_iter).c_str()); + } + + for (auto extras_iter = kGmaAdapterExtras.begin(); + extras_iter != kGmaAdapterExtras.end(); ++extras_iter) { + request.add_extra(kAdNetworkExtrasClassName, extras_iter->first.c_str(), + extras_iter->second.c_str()); + } + + // Content URL + request.set_content_url(kContentUrl); + + // Neighboring Content URLs + request.add_neighboring_content_urls(kNeighboringContentURLs); + + return request; +} + +firebase::Variant FirebaseGmaTest::GetVariantMap() { + firebase::Variant in_key = firebase::Variant::FromMutableString("inner_key"); + firebase::Variant in_val = firebase::Variant::FromMutableString("inner_val"); + firebase::Variant out_key = firebase::Variant::FromMutableString("outer_key"); + + firebase::Variant out_val = firebase::Variant::EmptyMap(); + out_val.map()[in_key] = in_val; + + firebase::Variant variant_map = firebase::Variant::EmptyMap(); + variant_map.map()[out_key] = out_val; + + return variant_map; +} + +FirebaseGmaMinimalTest::FirebaseGmaMinimalTest() {} + +FirebaseGmaMinimalTest::~FirebaseGmaMinimalTest() {} + +FirebaseGmaUITest::FirebaseGmaUITest() {} + +FirebaseGmaUITest::~FirebaseGmaUITest() {} + +void FirebaseGmaUITest::SetUp() { + TEST_REQUIRES_USER_INTERACTION; + FirebaseTest::SetUp(); + // This example uses ad units that are specially configured to return test ads + // for every request. When using your own ad unit IDs, however, it's important + // to register the device IDs associated with any devices that will be used to + // test the app. This ensures that regardless of the ad unit ID, those + // devices will always receive test ads in compliance with GMA policy. + // + // Device IDs can be obtained by checking the logcat or the Xcode log while + // debugging. They appear as a long string of hex characters. + firebase::gma::RequestConfiguration request_configuration; + request_configuration.test_device_ids = kTestDeviceIDs; + request_configuration.test_device_ids.push_back(GetDebugDeviceId()); + firebase::gma::SetRequestConfiguration(request_configuration); +} + +FirebaseGmaPreInitializationTests::FirebaseGmaPreInitializationTests() {} + +FirebaseGmaPreInitializationTests::~FirebaseGmaPreInitializationTests() {} + +void FirebaseGmaPreInitializationTests::SetUp() { FirebaseTest::SetUp(); } + +void FirebaseGmaPreInitializationTests::SetUpTestSuite() { + LogDebug("Initialize Firebase App."); + FindFirebaseConfig(FIREBASE_CONFIG_STRING); +#if defined(ANDROID) + shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + shared_app_ = ::firebase::App::Create(); +#endif // defined(ANDROID) +} + +// Test cases below. + +TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { + // Don't initialize mediation in this test so that a later test can still + // verify that mediation has not been initialized. + firebase::gma::DisableMediationInitialization(); + LogDebug("Initializing GMA without a Firebase App."); + firebase::InitResult result; +#if defined(ANDROID) + ::firebase::gma::Initialize(app_framework::GetJniEnv(), + app_framework::GetActivity(), &result); +#else // !defined(ANDROID) + ::firebase::gma::Initialize(&result); +#endif // defined(ANDROID) + EXPECT_EQ(result, ::firebase::kInitResultSuccess); + WaitForCompletion(firebase::gma::InitializeLastResult(), "gma::Initialize"); + LogDebug("Successfully initialized GMA."); + + LogDebug("Shutdown GMA."); + firebase::gma::Terminate(); +} + +TEST_F(FirebaseGmaPreInitializationTests, TestDisableMediationInitialization) { + // Note: This test should be disabled or put in an entirely different test + // binrary if we ever wish to test mediation in this application. + firebase::gma::DisableMediationInitialization(); + + // Ensure that GMA can initialize. + InitializeGma(shared_app_); + auto initialize_future = firebase::gma::InitializeLastResult(); + WaitForCompletion(initialize_future, "gma::Initialize"); + ASSERT_NE(initialize_future.result(), nullptr); + EXPECT_EQ(*initialize_future.result(), + firebase::gma::GetInitializationStatus()); + +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Check to see that only one Adapter was initialized, the base GMA adapter. + // Note: DisableMediationInitialization is only implemented on iOS. + std::map adapter_status = + firebase::gma::GetInitializationStatus().GetAdapterStatusMap(); + EXPECT_EQ(adapter_status.size(), 1); + EXPECT_THAT( + adapter_status, + Contains( + Pair(kGmaClassName, + Property(&firebase::gma::AdapterStatus::is_initialized, true)))) + << "Expected adapter class '" << kGmaClassName << "' is not loaded."; +#endif +} + +TEST_F(FirebaseGmaTest, TestInitializationStatus) { + // Ensure Initialize()'s result matches GetInitializationStatus(). + auto initialize_future = firebase::gma::InitializeLastResult(); + WaitForCompletion(initialize_future, "gma::Initialize"); + ASSERT_NE(initialize_future.result(), nullptr); + EXPECT_EQ(*initialize_future.result(), + firebase::gma::GetInitializationStatus()); + + for (auto adapter_status : + firebase::gma::GetInitializationStatus().GetAdapterStatusMap()) { + LogDebug("GMA Mediation Adapter '%s' %s (latency %d ms): %s", + adapter_status.first.c_str(), + (adapter_status.second.is_initialized() ? "loaded" : "NOT loaded"), + adapter_status.second.latency(), + adapter_status.second.description().c_str()); + } + + // Confirm that the default Google Mobile Ads SDK class name shows up in the + // list. It should either be is_initialized = true, or description should say + // "Timeout" (this is a special case we are using to deflake this test on + // Android emulator). + EXPECT_THAT( + initialize_future.result()->GetAdapterStatusMap(), + Contains(Pair( + kGmaClassName, + AnyOf(Property(&firebase::gma::AdapterStatus::is_initialized, true), + Property(&firebase::gma::AdapterStatus::description, + HasSubstr("Timeout")))))) + << "Expected adapter class '" << kGmaClassName << "' is not loaded."; +} + +TEST_F(FirebaseGmaPreInitializationTests, TestDisableSDKCrashReporting) { + // We can't test to see if this method successfully reconfigures crash + // reporting, but we're still calling it as a sanity check and to ensure + // the symbol exists in the library. + firebase::gma::DisableSDKCrashReporting(); +} + +TEST_F(FirebaseGmaTest, TestSetAppKeyEnabled) { + // We can't test to see if this method successfully enables/disables + // the app key, but we're still calling it as a sanity check and to + // ensure the symbol exists in the library. + firebase::gma::SetIsSameAppKeyEnabled(true); +} + +TEST_F(FirebaseGmaTest, TestGetAdRequest) { GetAdRequest(); } + +TEST_F(FirebaseGmaTest, TestGetVariantMap) { GetVariantMap(); } + +TEST_F(FirebaseGmaTest, TestGetAdRequestValues) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::AdRequest request = GetAdRequest(); + + // Content URL. + EXPECT_TRUE(request.content_url() == std::string(kContentUrl)); + + // Extras. + std::map > configured_extras = + request.extras(); + + EXPECT_EQ(configured_extras.size(), 1); + for (auto extras_name_iter = configured_extras.begin(); + extras_name_iter != configured_extras.end(); ++extras_name_iter) { + // Confirm class name. + const std::string class_name = extras_name_iter->first; + EXPECT_EQ(class_name, kAdNetworkExtrasClassName); + + // Grab the extras. + const std::map& configured_extras = + extras_name_iter->second; + EXPECT_EQ(configured_extras.size(), kGmaAdapterExtras.size()); + + // Check the extra key value pairs. + for (auto constant_extras_iter = kGmaAdapterExtras.begin(); + constant_extras_iter != kGmaAdapterExtras.end(); + ++constant_extras_iter) { + // Ensure the configured value matches the constant for the same key. + EXPECT_EQ(configured_extras.at(constant_extras_iter->first), + constant_extras_iter->second); + } + } + + const std::unordered_set configured_keywords = + request.keywords(); + EXPECT_EQ(configured_keywords.size(), kKeywords.size()); + for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); + ++keyword_iter) { + EXPECT_TRUE(configured_keywords.find(*keyword_iter) != + configured_keywords.end()); + } + + const std::unordered_set configured_neighboring_content_urls = + request.neighboring_content_urls(); + EXPECT_EQ(configured_neighboring_content_urls.size(), + kNeighboringContentURLs.size()); + for (auto url_iter = kNeighboringContentURLs.begin(); + url_iter != kNeighboringContentURLs.end(); ++url_iter) { + EXPECT_TRUE(configured_neighboring_content_urls.find(*url_iter) != + configured_neighboring_content_urls.end()); + } +} + +// A listener to detect when the AdInspector has been closed. Additionally, +// checks for errors when opening the AdInspector while it's already open. +class TestAdInspectorClosedListener + : public firebase::gma::AdInspectorClosedListener { + public: + TestAdInspectorClosedListener() + : num_closed_events_(0), num_successful_results_(0) {} + + // Called when the user clicked the ad. + void OnAdInspectorClosed(const firebase::gma::AdResult& ad_result) override { + ++num_closed_events_; + if (ad_result.is_successful()) { + ++num_successful_results_; + } else { +#if defined(ANDROID) + EXPECT_EQ(ad_result.ad_error().code(), + firebase::gma::kAdErrorCodeInsepctorAlreadyOpen); + EXPECT_STREQ(ad_result.ad_error().message().c_str(), + "Ad inspector cannot be opened because it is already open."); +#else + // The iOS GMA SDK returns internal errors for all AdInspector failures. + EXPECT_EQ(ad_result.ad_error().code(), + firebase::gma::kAdErrorCodeInternalError); + EXPECT_STREQ(ad_result.ad_error().message().c_str(), + "Ad Inspector cannot be opened because it is already open."); +#endif + } + } + + uint8_t num_closed_events() const { return num_closed_events_; } + uint8_t num_successful_results() const { return num_successful_results_; } + + private: + uint8_t num_closed_events_; + uint8_t num_successful_results_; +}; + +// This is for manual test only +// Ensure we can open the AdInspector and listen to its events. +TEST_F(FirebaseGmaTest, TestAdInspector) { + TEST_REQUIRES_USER_INTERACTION; + TestAdInspectorClosedListener listener; + + firebase::gma::OpenAdInspector(app_framework::GetWindowController(), + &listener); + + // Call OpenAdInspector, even on Desktop (above), to ensure the stub linked + // correctly. However, the rest of the testing is mobile-only beahvior. + SKIP_TEST_ON_DESKTOP; + + // Open the inspector a second time to generate a + // kAdErrorCodeInsepctorAlreadyOpen result. + app_framework::ProcessEvents(2000); + + firebase::gma::OpenAdInspector(app_framework::GetWindowController(), + &listener); + + while (listener.num_closed_events() < 2) { + app_framework::ProcessEvents(2000); + } + + EXPECT_EQ(listener.num_successful_results(), 1); +} + +// A simple listener to help test changes to a AdViews. +class TestBoundingBoxListener + : public firebase::gma::AdViewBoundingBoxListener { + public: + void OnBoundingBoxChanged(firebase::gma::AdView* ad_view, + firebase::gma::BoundingBox box) override { + bounding_box_changes_.push_back(box); + } + std::vector bounding_box_changes_; +}; + +// A simple listener to help test changes an Ad. +class TestAdListener : public firebase::gma::AdListener { + public: + TestAdListener() + : num_on_ad_clicked_(0), + num_on_ad_closed_(0), + num_on_ad_impression_(0), + num_on_ad_opened_(0) {} + + void OnAdClicked() override { num_on_ad_clicked_++; } + void OnAdClosed() override { num_on_ad_closed_++; } + void OnAdImpression() override { num_on_ad_impression_++; } + void OnAdOpened() override { num_on_ad_opened_++; } + + int num_on_ad_clicked_; + int num_on_ad_closed_; + int num_on_ad_impression_; + int num_on_ad_opened_; +}; + +// A simple listener track FullScreen presentation changes. +class TestFullScreenContentListener + : public firebase::gma::FullScreenContentListener { + public: + TestFullScreenContentListener() + : num_on_ad_clicked_(0), + num_on_ad_dismissed_full_screen_content_(0), + num_on_ad_failed_to_show_full_screen_content_(0), + num_on_ad_impression_(0), + num_on_ad_showed_full_screen_content_(0) {} + + int num_ad_clicked() const { return num_on_ad_clicked_; } + int num_ad_dismissed() const { + return num_on_ad_dismissed_full_screen_content_; + } + int num_ad_failed_to_show_content() const { + return num_on_ad_failed_to_show_full_screen_content_; + } + int num_ad_impressions() const { return num_on_ad_impression_; } + int num_ad_showed_content() const { + return num_on_ad_showed_full_screen_content_; + } + + void OnAdClicked() override { num_on_ad_clicked_++; } + + void OnAdDismissedFullScreenContent() override { + num_on_ad_dismissed_full_screen_content_++; + } + + void OnAdFailedToShowFullScreenContent( + const firebase::gma::AdError& ad_error) override { + num_on_ad_failed_to_show_full_screen_content_++; + failure_codes_.push_back(ad_error.code()); + } + + void OnAdImpression() override { num_on_ad_impression_++; } + + void OnAdShowedFullScreenContent() override { + num_on_ad_showed_full_screen_content_++; + } + + const std::vector failure_codes() const { + return failure_codes_; + } + + private: + int num_on_ad_clicked_; + int num_on_ad_dismissed_full_screen_content_; + int num_on_ad_failed_to_show_full_screen_content_; + int num_on_ad_impression_; + int num_on_ad_showed_full_screen_content_; + + std::vector failure_codes_; +}; + +// A simple listener track UserEarnedReward events. +class TestUserEarnedRewardListener + : public firebase::gma::UserEarnedRewardListener { + public: + TestUserEarnedRewardListener() : num_on_user_earned_reward_(0) {} + + int num_earned_rewards() const { return num_on_user_earned_reward_; } + + void OnUserEarnedReward(const firebase::gma::AdReward& reward) override { + ++num_on_user_earned_reward_; + EXPECT_EQ(reward.type(), "coins"); + EXPECT_EQ(reward.amount(), 10); + } + + private: + int num_on_user_earned_reward_; +}; + +// A simple listener track ad pay events. +class TestPaidEventListener : public firebase::gma::PaidEventListener { + public: + TestPaidEventListener() : num_on_paid_event_(0) {} + + int num_paid_events() const { return num_on_paid_event_; } + + void OnPaidEvent(const firebase::gma::AdValue& value) override { + ++num_on_paid_event_; + // These are the values for GMA test ads. If they change then we should + // alter the test to match the new expected values. + EXPECT_EQ(value.currency_code(), "USD"); + EXPECT_EQ(value.value_micros(), 0); + } + int num_on_paid_event_; +}; + +TEST_F(FirebaseGmaTest, TestAdSize) { + uint32_t kWidth = 50; + uint32_t kHeight = 10; + + using firebase::gma::AdSize; + + const AdSize adaptive_landscape = + AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(kWidth); + EXPECT_EQ(adaptive_landscape.width(), kWidth); + EXPECT_EQ(adaptive_landscape.height(), 0); + EXPECT_EQ(adaptive_landscape.type(), AdSize::kTypeAnchoredAdaptive); + EXPECT_EQ(adaptive_landscape.orientation(), AdSize::kOrientationLandscape); + + const AdSize adaptive_portrait = + AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(kWidth); + EXPECT_EQ(adaptive_portrait.width(), kWidth); + EXPECT_EQ(adaptive_portrait.height(), 0); + EXPECT_EQ(adaptive_portrait.type(), AdSize::kTypeAnchoredAdaptive); + EXPECT_EQ(adaptive_portrait.orientation(), AdSize::kOrientationPortrait); + + EXPECT_FALSE(adaptive_portrait == adaptive_landscape); + EXPECT_TRUE(adaptive_portrait != adaptive_landscape); + + const firebase::gma::AdSize adaptive_current = + AdSize::GetCurrentOrientationAnchoredAdaptiveBannerAdSize(kWidth); + EXPECT_EQ(adaptive_current.width(), kWidth); + EXPECT_EQ(adaptive_current.height(), 0); + EXPECT_EQ(adaptive_current.type(), AdSize::kTypeAnchoredAdaptive); + EXPECT_EQ(adaptive_current.orientation(), AdSize::kOrientationCurrent); + + const firebase::gma::AdSize custom_ad_size(kWidth, kHeight); + EXPECT_EQ(custom_ad_size.width(), kWidth); + EXPECT_EQ(custom_ad_size.height(), kHeight); + EXPECT_EQ(custom_ad_size.type(), AdSize::kTypeStandard); + EXPECT_EQ(custom_ad_size.orientation(), AdSize::kOrientationCurrent); + + const AdSize custom_ad_size_2(kWidth, kHeight); + EXPECT_TRUE(custom_ad_size == custom_ad_size_2); + EXPECT_FALSE(custom_ad_size != custom_ad_size_2); + + const AdSize banner = AdSize::kBanner; + EXPECT_EQ(banner.width(), 320); + EXPECT_EQ(banner.height(), 50); + EXPECT_EQ(banner.type(), AdSize::kTypeStandard); + EXPECT_EQ(banner.orientation(), AdSize::kOrientationCurrent); + + const AdSize fullbanner = AdSize::kFullBanner; + EXPECT_EQ(fullbanner.width(), 468); + EXPECT_EQ(fullbanner.height(), 60); + EXPECT_EQ(fullbanner.type(), AdSize::kTypeStandard); + EXPECT_EQ(fullbanner.orientation(), AdSize::kOrientationCurrent); + + const AdSize leaderboard = AdSize::kLeaderboard; + EXPECT_EQ(leaderboard.width(), 728); + EXPECT_EQ(leaderboard.height(), 90); + EXPECT_EQ(leaderboard.type(), AdSize::kTypeStandard); + EXPECT_EQ(leaderboard.orientation(), AdSize::kOrientationCurrent); + + const AdSize medium_rectangle = AdSize::kMediumRectangle; + EXPECT_EQ(medium_rectangle.width(), 300); + EXPECT_EQ(medium_rectangle.height(), 250); + EXPECT_EQ(medium_rectangle.type(), AdSize::kTypeStandard); + EXPECT_EQ(medium_rectangle.orientation(), AdSize::kOrientationCurrent); +} + +TEST_F(FirebaseGmaTest, TestRequestConfigurationSetGetEmptyConfig) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::RequestConfiguration set_configuration; + firebase::gma::SetRequestConfiguration(set_configuration); + firebase::gma::RequestConfiguration retrieved_configuration = + firebase::gma::GetRequestConfiguration(); + + EXPECT_EQ( + retrieved_configuration.max_ad_content_rating, + firebase::gma::RequestConfiguration::kMaxAdContentRatingUnspecified); + EXPECT_EQ( + retrieved_configuration.tag_for_child_directed_treatment, + firebase::gma::RequestConfiguration::kChildDirectedTreatmentUnspecified); + EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, + firebase::gma::RequestConfiguration::kUnderAgeOfConsentUnspecified); + EXPECT_EQ(retrieved_configuration.test_device_ids.size(), 0); +} + +TEST_F(FirebaseGmaTest, TestRequestConfigurationSetGet) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::RequestConfiguration set_configuration; + set_configuration.max_ad_content_rating = + firebase::gma::RequestConfiguration::kMaxAdContentRatingPG; + set_configuration.tag_for_child_directed_treatment = + firebase::gma::RequestConfiguration::kChildDirectedTreatmentTrue; + set_configuration.tag_for_under_age_of_consent = + firebase::gma::RequestConfiguration::kUnderAgeOfConsentFalse; + set_configuration.test_device_ids.push_back("1"); + set_configuration.test_device_ids.push_back("2"); + set_configuration.test_device_ids.push_back("3"); + firebase::gma::SetRequestConfiguration(set_configuration); + + firebase::gma::RequestConfiguration retrieved_configuration = + firebase::gma::GetRequestConfiguration(); + + EXPECT_EQ(retrieved_configuration.max_ad_content_rating, + firebase::gma::RequestConfiguration::kMaxAdContentRatingPG); + +#if defined(ANDROID) + EXPECT_EQ(retrieved_configuration.tag_for_child_directed_treatment, + firebase::gma::RequestConfiguration::kChildDirectedTreatmentTrue); + EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, + firebase::gma::RequestConfiguration::kUnderAgeOfConsentFalse); +#else // iOS + // iOS doesn't allow for the querying of these values. + EXPECT_EQ( + retrieved_configuration.tag_for_child_directed_treatment, + firebase::gma::RequestConfiguration::kChildDirectedTreatmentUnspecified); + EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, + firebase::gma::RequestConfiguration::kUnderAgeOfConsentUnspecified); +#endif + + EXPECT_EQ(retrieved_configuration.test_device_ids.size(), 3); + EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), + retrieved_configuration.test_device_ids.end(), "1")); + EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), + retrieved_configuration.test_device_ids.end(), "2")); + EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), + retrieved_configuration.test_device_ids.end(), "3")); +} + +// Simple Load Tests as a sanity check. These don't show the ad, just +// ensure that we can load them before diving into the interactive tests. +TEST_F(FirebaseGmaTest, TestAdViewLoadAd) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + firebase::Future load_ad_future; + const firebase::gma::AdResult* result_ptr = nullptr; + + load_ad_future = ad_view->LoadAd(GetAdRequest()); + WaitForCompletion(load_ad_future, "LoadAd"); + + result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + + EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); + EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); + EXPECT_EQ(ad_view->ad_size().type(), firebase::gma::AdSize::kTypeStandard); + + load_ad_future.Release(); + WaitForCompletion(ad_view->Destroy(), "Destroy"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdLoad) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::InterstitialAd* interstitial = + new firebase::gma::InterstitialAd(); + + WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the InterstitialAd is initialized, load an ad. + firebase::Future load_ad_future = + interstitial->LoadAd(kInterstitialAdUnit, GetAdRequest()); + + // This test behaves differently if it's running in UI mode + // (manually on a device) or in non-UI mode (via automated tests). + if (ShouldRunUITests()) { + // Run in manual mode: fail if any error occurs. + WaitForCompletion(load_ad_future, "LoadAd"); + } else { + // Run in automated test mode: don't fail if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + } + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + } + + load_ad_future.Release(); + delete interstitial; +} + +TEST_F(FirebaseGmaTest, TestRewardedAdLoad) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the RewardedAd is initialized, load an ad. + firebase::Future load_ad_future = + rewarded->LoadAd(kRewardedAdUnit, GetAdRequest()); + + // This test behaves differently if it's running in UI mode + // (manually on a device) or in non-UI mode (via automated tests). + if (ShouldRunUITests()) { + // Run in manual mode: fail if any error occurs. + WaitForCompletion(load_ad_future, "LoadAd"); + } else { + // Run in automated test mode: don't fail if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + } + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + // In UI mode, or in non-UI mode if a NoFill error didn't occur, check that + // the ad loaded correctly. + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + } + load_ad_future.Release(); + delete rewarded; +} + +TEST_F(FirebaseGmaTest, TestNativeAdLoad) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the NativeAd is initialized, load an ad. + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); + + // Don't fail loadAd, if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + + // Check image assets. + // Native ads usually contain only one large image asset. + // Check the validity of the first asset from the vector. + EXPECT_FALSE(native_ad->images().empty()); + EXPECT_FALSE(native_ad->images().at(0).image_uri().empty()); + EXPECT_GT(native_ad->images().at(0).scale(), 0); + + // Try loading large image asset. + firebase::Future load_image_future = + native_ad->images().at(0).LoadImage(); + WaitForCompletion(load_image_future, "LoadImage"); + const firebase::gma::ImageResult* img_result_ptr = + load_image_future.result(); + ASSERT_NE(img_result_ptr, nullptr); + EXPECT_TRUE(img_result_ptr->is_successful()); + EXPECT_GT(img_result_ptr->image().size(), 0); + + load_image_future.Release(); + } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { + LogWarning("LoadAd returned NoFill in TestNativeAdLoad"); + } + + load_ad_future.Release(); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestCreateQueryInfo) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); + + WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + firebase::gma::AdRequest request = GetAdRequest(); + // Set the requester type to 8. QueryInfo gets generated without a + // query_info_type set, but throws a warning that it is missing. + request.add_extra(kAdNetworkExtrasClassName, "query_info_type", + "requester_type_8"); + // When the QueryInfo is initialized, generate a query info string. + firebase::Future create_query_info_future = + query_info->CreateQueryInfo(firebase::gma::kAdFormatNative, request); + + WaitForCompletion(create_query_info_future, "CreateQueryInfo"); + + if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::QueryInfoResult* result_ptr = + create_query_info_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->query_info().empty()); + } + + create_query_info_future.Release(); + delete query_info; +} + +TEST_F(FirebaseGmaTest, TestCreateQueryInfoWithAdUnit) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); + + WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + firebase::gma::AdRequest request = GetAdRequest(); + // Set the requester type to 8. QueryInfo gets generated without a + // query_info_type set, but throws a warning that it is missing. + request.add_extra(kAdNetworkExtrasClassName, "query_info_type", + "requester_type_8"); + // When the QueryInfo is initialized, generate a query info string. + // Providing a bad/empty ad unit does not affect the query info generation. + firebase::Future create_query_info_future = + query_info->CreateQueryInfoWithAdUnit(firebase::gma::kAdFormatNative, + request, kNativeAdUnit); + + WaitForCompletion(create_query_info_future, "CreateQueryInfoWithAdUnit"); + + if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::QueryInfoResult* result_ptr = + create_query_info_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->query_info().empty()); + } + + create_query_info_future.Release(); + delete query_info; +} + +// Interactive test section. These have been placed up front so that the +// tester doesn't get bored waiting for them. +TEST_F(FirebaseGmaUITest, TestAdViewAdOpenedAdClosed) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + // Set the listener. + TestAdListener ad_listener; + ad_view->SetAdListener(&ad_listener); + + TestPaidEventListener paid_event_listener; + ad_view->SetPaidEventListener(&paid_event_listener); + + // Load the AdView ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + ad_view->LoadAd(request); + WaitForCompletion(load_ad_future, "LoadAd"); + + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + WaitForCompletion(ad_view->Show(), "Show 0"); + + // Ad Events differ per platform. See the following for more info: + // https://www.googblogs.com/google-mobile-ads-sdk-a-note-on-ad-click-events/ + // and https://groups.google.com/g/google-admob-ads-sdk/c/lzdt5szxSVU +#if defined(ANDROID) + LogDebug("Click the Ad, and then close the ad to continue"); + + while (ad_listener.num_on_ad_opened_ == 0) { + app_framework::ProcessEvents(1000); + } + + while (ad_listener.num_on_ad_closed_ == 0) { + app_framework::ProcessEvents(1000); + } + + // Ensure all of the expected events were triggered on Android. + EXPECT_EQ(ad_listener.num_on_ad_clicked_, 1); + EXPECT_EQ(ad_listener.num_on_ad_impression_, 1); + EXPECT_EQ(ad_listener.num_on_ad_opened_, 1); + EXPECT_EQ(ad_listener.num_on_ad_closed_, 1); + EXPECT_EQ(paid_event_listener.num_paid_events(), 1); +#else + LogDebug("Click the Ad, and then close the ad to continue"); + + while (ad_listener.num_on_ad_clicked_ == 0) { + app_framework::ProcessEvents(1000); + } + + LogDebug("Waiting for a moment to ensure all callbacks are recorded."); + app_framework::ProcessEvents(2000); + + // Ensure all of the expected events were triggered on iOS. + EXPECT_EQ(ad_listener.num_on_ad_clicked_, 1); + EXPECT_EQ(ad_listener.num_on_ad_impression_, 1); + EXPECT_EQ(paid_event_listener.num_paid_events(), 1); + EXPECT_EQ(ad_listener.num_on_ad_opened_, 0); + EXPECT_EQ(ad_listener.num_on_ad_closed_, 0); +#endif + } + + load_ad_future.Release(); + ad_view->SetAdListener(nullptr); + ad_view->SetPaidEventListener(nullptr); + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; +} + +TEST_F(FirebaseGmaUITest, TestInterstitialAdLoadAndShow) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::InterstitialAd* interstitial = + new firebase::gma::InterstitialAd(); + + WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + TestFullScreenContentListener content_listener; + interstitial->SetFullScreenContentListener(&content_listener); + + TestPaidEventListener paid_event_listener; + interstitial->SetPaidEventListener(&paid_event_listener); + + // When the InterstitialAd is initialized, load an ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + interstitial->LoadAd(kInterstitialAdUnit, request); + WaitForCompletion(load_ad_future, "LoadAd"); + + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + WaitForCompletion(interstitial->Show(), "Show"); + + LogDebug("Click the Ad, and then return to the app to continue"); + + while (content_listener.num_ad_dismissed() == 0) { + app_framework::ProcessEvents(1000); + } + + LogDebug("Waiting for a moment to ensure all callbacks are recorded."); + app_framework::ProcessEvents(2000); + + EXPECT_EQ(content_listener.num_ad_clicked(), 1); + EXPECT_EQ(content_listener.num_ad_showed_content(), 1); + EXPECT_EQ(content_listener.num_ad_impressions(), 1); + EXPECT_EQ(content_listener.num_ad_failed_to_show_content(), 0); + EXPECT_EQ(content_listener.num_ad_dismissed(), 1); + EXPECT_EQ(paid_event_listener.num_paid_events(), 1); + +#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) + // Show the Ad again. Note: Android's Interstitial ads fail silently + // when attempting to show the ad twice. + LogDebug("Attempting to show ad again, checking for correct error result."); + WaitForCompletion(interstitial->Show(), "Show"); + app_framework::ProcessEvents(5000); + EXPECT_THAT(content_listener.failure_codes(), + ElementsAre(firebase::gma::kAdErrorCodeAdAlreadyUsed)); +#endif // TARGET_OS_IPHONE + } + + load_ad_future.Release(); + interstitial->SetFullScreenContentListener(nullptr); + interstitial->SetPaidEventListener(nullptr); + + delete interstitial; +} + +TEST_F(FirebaseGmaUITest, TestRewardedAdLoadAndShow) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + // TODO(@drsanta): remove when GMA whitelists CI devices. + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + TestFullScreenContentListener content_listener; + rewarded->SetFullScreenContentListener(&content_listener); + + TestPaidEventListener paid_event_listener; + rewarded->SetPaidEventListener(&paid_event_listener); + + // When the RewardedAd is initialized, load an ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + rewarded->LoadAd(kRewardedAdUnit, request); + WaitForCompletion(load_ad_future, "LoadAd"); + + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + firebase::gma::RewardedAd::ServerSideVerificationOptions options; + // We cannot programmatically verify that the GMA phone SDKs marshal + // these values properly (there are no get methods). At least invoke the + // method to ensure least we can set them without any exceptions occurring. + options.custom_data = "custom data"; + options.user_id = "123456"; + rewarded->SetServerSideVerificationOptions(options); + + TestUserEarnedRewardListener earned_reward_listener; + WaitForCompletion(rewarded->Show(&earned_reward_listener), "Show"); + + LogDebug( + "Wait for the Ad to finish playing, click the ad, return to the ad, " + "then close the ad to continue."); + + while (content_listener.num_ad_dismissed() == 0) { + app_framework::ProcessEvents(1000); + } + + LogDebug("Waiting for a moment to ensure all callbacks are recorded."); + app_framework::ProcessEvents(2000); + + // If not running the UI test in CI (running manually), keep this check. + // Else running the UI test in CI, skip this check. + if (!ShouldRunUITests()) { + EXPECT_EQ(content_listener.num_ad_clicked(), 1); + } + EXPECT_EQ(content_listener.num_ad_showed_content(), 1); + EXPECT_EQ(content_listener.num_ad_impressions(), 1); + EXPECT_EQ(content_listener.num_ad_dismissed(), 1); + EXPECT_EQ(content_listener.num_ad_failed_to_show_content(), 0); + EXPECT_EQ(earned_reward_listener.num_earned_rewards(), 1); + EXPECT_EQ(paid_event_listener.num_paid_events(), 1); + + // Show the Ad again + LogDebug("Attempting to show ad again, checking for correct error result."); + WaitForCompletion(rewarded->Show(&earned_reward_listener), "Show"); + app_framework::ProcessEvents(2000); + EXPECT_THAT(content_listener.failure_codes(), + testing::ElementsAre(firebase::gma::kAdErrorCodeAdAlreadyUsed)); + } + + load_ad_future.Release(); + rewarded->SetFullScreenContentListener(nullptr); + rewarded->SetPaidEventListener(nullptr); + + delete rewarded; +} + +// Other AdView Tests + +TEST_F(FirebaseGmaTest, TestAdViewLoadAdEmptyAdRequest) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + firebase::gma::AdRequest request; + firebase::Future load_ad_future; + const firebase::gma::AdResult* result_ptr = nullptr; + + load_ad_future = ad_view->LoadAd(request); + WaitForCompletion(load_ad_future, "LoadAd"); + result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + + EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); + EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); + EXPECT_EQ(ad_view->ad_size().type(), firebase::gma::AdSize::kTypeStandard); + + load_ad_future.Release(); + WaitForCompletion(ad_view->Destroy(), "Destroy"); + + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewLoadAdAnchorAdaptiveAd) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + using firebase::gma::AdSize; + AdSize banner_ad_size = + AdSize::GetCurrentOrientationAnchoredAdaptiveBannerAdSize(kBannerWidth); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); + + const AdSize ad_size = ad_view->ad_size(); + EXPECT_EQ(ad_size.width(), kBannerWidth); + EXPECT_NE(ad_size.height(), 0); + EXPECT_EQ(ad_size.type(), AdSize::kTypeAnchoredAdaptive); + EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); + WaitForCompletion(ad_view->Destroy(), "Destroy"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewLoadAdInlineAdaptiveAd) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + using firebase::gma::AdSize; + + using firebase::gma::AdSize; + AdSize banner_ad_size = + AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(kBannerWidth); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); + + const AdSize ad_size = ad_view->ad_size(); + EXPECT_EQ(ad_size.width(), kBannerWidth); + EXPECT_NE(ad_size.height(), 0); + EXPECT_EQ(ad_size.type(), AdSize::kTypeInlineAdaptive); + EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); + WaitForCompletion(ad_view->Destroy(), "Destroy"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewLoadAdGetInlineAdaptiveBannerMaxHeight) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + using firebase::gma::AdSize; + AdSize banner_ad_size = + AdSize::GetInlineAdaptiveBannerAdSize(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); + + const AdSize ad_size = ad_view->ad_size(); + EXPECT_EQ(ad_size.width(), kBannerWidth); + EXPECT_NE(ad_size.height(), 0); + EXPECT_TRUE(ad_size.height() <= kBannerHeight); + EXPECT_EQ(ad_size.type(), AdSize::kTypeInlineAdaptive); + EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); + WaitForCompletion(ad_view->Destroy(), "Destroy"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewLoadAdDestroyNotCalled) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewAdSizeCompareOp) { + using firebase::gma::AdSize; + EXPECT_TRUE(AdSize(50, 100) == AdSize(50, 100)); + EXPECT_TRUE(AdSize(100, 50) == AdSize(100, 50)); + EXPECT_FALSE(AdSize(50, 100) == AdSize(100, 50)); + EXPECT_FALSE(AdSize(10, 10) == AdSize(50, 50)); + + EXPECT_FALSE(AdSize(50, 100) != AdSize(50, 100)); + EXPECT_FALSE(AdSize(100, 50) != AdSize(100, 50)); + EXPECT_TRUE(AdSize(50, 100) != AdSize(100, 50)); + EXPECT_TRUE(AdSize(10, 10) != AdSize(50, 50)); + + EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == + AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100)); + EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != + AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100)); + + EXPECT_TRUE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) == + AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); + EXPECT_FALSE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) != + AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); + + EXPECT_TRUE(AdSize::GetInlineAdaptiveBannerAdSize(100, 50) == + AdSize::GetInlineAdaptiveBannerAdSize(100, 50)); + EXPECT_FALSE(AdSize::GetInlineAdaptiveBannerAdSize(100, 50) != + AdSize::GetInlineAdaptiveBannerAdSize(100, 50)); + + EXPECT_TRUE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) == + AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); + EXPECT_FALSE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) != + AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); + + EXPECT_TRUE(AdSize::GetPortraitInlineAdaptiveBannerAdSize(100) == + AdSize::GetPortraitInlineAdaptiveBannerAdSize(100)); + EXPECT_TRUE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) == + AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); + EXPECT_TRUE(AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(100) == + AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(100)); + + EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == + AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); + EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != + AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); + + EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == + AdSize(100, 100)); + EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != + AdSize(100, 100)); + + EXPECT_FALSE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) == + AdSize(100, 100)); + EXPECT_TRUE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) != + AdSize(100, 100)); +} + +TEST_F(FirebaseGmaTest, TestAdViewDestroyBeforeInitialization) { + SKIP_TEST_ON_DESKTOP; + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); +} + +TEST_F(FirebaseGmaTest, TestAdViewAdSizeBeforeInitialization) { + SKIP_TEST_ON_DESKTOP; + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + const firebase::gma::AdSize& ad_size = firebase::gma::AdSize(0, 0); + EXPECT_TRUE(ad_view->ad_size() == ad_size); + + WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); +} + +TEST_F(FirebaseGmaTest, TestAdView) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + EXPECT_TRUE(ad_view->ad_size() == banner_ad_size); + + // Set the listener. + TestBoundingBoxListener bounding_box_listener; + ad_view->SetBoundingBoxListener(&bounding_box_listener); + PauseForVisualInspectionAndCallbacks(); + + int expected_num_bounding_box_changes = 0; + EXPECT_EQ(expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // Load the AdView ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + ad_view->LoadAd(request); + WaitForCompletion(load_ad_future, "LoadAd"); + + const bool ad_loaded = + load_ad_future.error() == firebase::gma::kAdErrorCodeNone; + + // Suppress the extensive testing below if the ad failed to load. + if (ad_loaded) { + EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); + EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); + EXPECT_EQ(expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + // Make the AdView visible. + WaitForCompletion(ad_view->Show(), "Show 0"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // Move to each of the six pre-defined positions. + WaitForCompletion(ad_view->SetPosition(firebase::gma::AdView::kPositionTop), + "SetPosition(Top)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionTop); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion( + ad_view->SetPosition(firebase::gma::AdView::kPositionTopLeft), + "SetPosition(TopLeft)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionTopLeft); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion( + ad_view->SetPosition(firebase::gma::AdView::kPositionTopRight), + "SetPosition(TopRight)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionTopRight); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion( + ad_view->SetPosition(firebase::gma::AdView::kPositionBottom), + "SetPosition(Bottom)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionBottom); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion( + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomLeft), + "SetPosition(BottomLeft)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionBottomLeft); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion( + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomRight), + "SetPosition(BottomRight)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionBottomRight); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // Move to some coordinates. + WaitForCompletion(ad_view->SetPosition(100, 300), "SetPosition(x0, y0)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionUndefined); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion(ad_view->SetPosition(100, 400), "SetPosition(x1, y1)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionUndefined); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // Try hiding and showing the AdView. + WaitForCompletion(ad_view->Hide(), "Hide 1"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion(ad_view->Show(), "Show 1"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // Move again after hiding/showing. + WaitForCompletion(ad_view->SetPosition(100, 300), "SetPosition(x2, y2)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionUndefined); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion(ad_view->SetPosition(100, 400), "SetPosition(x3, y3)"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(ad_view->bounding_box().position, + firebase::gma::AdView::kPositionUndefined); + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + WaitForCompletion(ad_view->Hide(), "Hide 2"); + PauseForVisualInspectionAndCallbacks(); + EXPECT_EQ(expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + LogDebug("Waiting for a moment to ensure all callbacks are recorded."); + app_framework::ProcessEvents(2000); + } + + // Clean up the ad object. + load_ad_future.Release(); + WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); + ad_view->SetBoundingBoxListener(nullptr); + delete ad_view; + + PauseForVisualInspectionAndCallbacks(); + + if (ad_loaded) { + // If the ad was show, do the final bounding box checks after the ad has + // been destroyed. +#if defined(ANDROID) || TARGET_OS_IPHONE + EXPECT_EQ(++expected_num_bounding_box_changes, + bounding_box_listener.bounding_box_changes_.size()); + + // As an extra check, all bounding boxes except the last should have the + // same size aspect ratio that we requested. For example if you requested a + // 320x50 banner, you can get one with the size 960x150. Use EXPECT_NEAR + // because the calculation can have a small bit of error. + double kAspectRatioAllowedError = 0.05; // Allow about 5% of error. + double expected_aspect_ratio = + static_cast(kBannerWidth) / static_cast(kBannerHeight); + for (int i = 0; i < bounding_box_listener.bounding_box_changes_.size() - 1; + ++i) { + double actual_aspect_ratio = + static_cast( + bounding_box_listener.bounding_box_changes_[i].width) / + static_cast( + bounding_box_listener.bounding_box_changes_[i].height); + EXPECT_NEAR(actual_aspect_ratio, expected_aspect_ratio, + kAspectRatioAllowedError) + << "AdView size " + << bounding_box_listener.bounding_box_changes_[i].width << "x" + << bounding_box_listener.bounding_box_changes_[i].height + << " does not have the same aspect ratio as requested size " + << kBannerWidth << "x" << kBannerHeight << "."; + } + + // And finally, the last bounding box change, when the AdView is deleted, + // should have invalid values (-1,-1, -1, -1). + EXPECT_TRUE( + bounding_box_listener.bounding_box_changes_.back().x == -1 && + bounding_box_listener.bounding_box_changes_.back().y == -1 && + bounding_box_listener.bounding_box_changes_.back().width == -1 && + bounding_box_listener.bounding_box_changes_.back().height == -1); +#endif // defined(ANDROID) || TARGET_OS_IPHONE + } +} + +TEST_F(FirebaseGmaTest, TestAdViewErrorNotInitialized) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + + WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd", + firebase::gma::kAdErrorCodeUninitialized); + + firebase::gma::AdView::Position position; + WaitForCompletion(ad_view->SetPosition(position), "SetPosition(position)", + firebase::gma::kAdErrorCodeUninitialized); + + WaitForCompletion(ad_view->SetPosition(0, 0), "SetPosition(x,y)", + firebase::gma::kAdErrorCodeUninitialized); + + WaitForCompletion(ad_view->Hide(), "Hide", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(ad_view->Show(), "Show", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(ad_view->Pause(), "Pause", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(ad_view->Resume(), "Resume", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewErrorAlreadyInitialized) { + SKIP_TEST_ON_DESKTOP; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + { + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + firebase::Future first_initialize = ad_view->Initialize( + app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); + firebase::Future second_initialize = ad_view->Initialize( + app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); + + WaitForCompletion(first_initialize, "First Initialize 1"); + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + + first_initialize.Release(); + second_initialize.Release(); + WaitForCompletion(ad_view->Destroy(), "Destroy AdView 1"); + delete ad_view; + } + + // Reverse the order of the completion waits. + { + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + firebase::Future first_initialize = ad_view->Initialize( + app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); + firebase::Future second_initialize = ad_view->Initialize( + app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); + + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + WaitForCompletion(first_initialize, "First Initialize 1"); + + first_initialize.Release(); + second_initialize.Release(); + WaitForCompletion(ad_view->Destroy(), "Destroy AdView 2"); + delete ad_view; + } +} + +TEST_F(FirebaseGmaTest, TestAdViewErrorLoadInProgress) { + SKIP_TEST_ON_DESKTOP; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + // Load the AdView ad. + // Note potential flake: this test assumes the attempt to load an ad + // won't resolve immediately. If it does then the result may be two + // successful ad loads instead of the expected + // kAdErrorCodeLoadInProgress error. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future first_load_ad = + ad_view->LoadAd(request); + firebase::Future second_load_ad = + ad_view->LoadAd(request); + + WaitForCompletion(second_load_ad, "Second LoadAd", + firebase::gma::kAdErrorCodeLoadInProgress); + WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); + + const firebase::gma::AdResult* result_ptr = second_load_ad.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeLoadInProgress); + EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); + EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + first_load_ad.Release(); + second_load_ad.Release(); + + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewErrorBadAdUnitId) { + SKIP_TEST_ON_DESKTOP; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBadAdUnit, banner_ad_size), + "Initialize"); + + // Load the AdView ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad = ad_view->LoadAd(request); + WaitForCompletion(load_ad, "LoadAd", + firebase::gma::kAdErrorCodeInvalidRequest); + + const firebase::gma::AdResult* result_ptr = load_ad.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeInvalidRequest); + + EXPECT_FALSE(result_ptr->ad_error().message().empty()); + EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); + + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + load_ad.Release(); + + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; +} + +TEST_F(FirebaseGmaTest, TestAdViewErrorBadExtrasClassName) { + SKIP_TEST_ON_DESKTOP; + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + + // Load the AdView ad. + firebase::gma::AdRequest request = GetAdRequest(); + request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); + WaitForCompletion(ad_view->LoadAd(request), "LoadAd", + firebase::gma::kAdErrorCodeAdNetworkClassLoadError); + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; +} + +// Other InterstitialAd Tests + +TEST_F(FirebaseGmaTest, TestInterstitialAdLoadEmptyRequest) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::InterstitialAd* interstitial = + new firebase::gma::InterstitialAd(); + + WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the InterstitialAd is initialized, load an ad. + firebase::gma::AdRequest request; + + firebase::Future load_ad_future = + interstitial->LoadAd(kInterstitialAdUnit, request); + + // This test behaves differently if it's running in UI mode + // (manually on a device) or in non-UI mode (via automated tests). + if (ShouldRunUITests()) { + // Run in manual mode: fail if any error occurs. + WaitForCompletion(load_ad_future, "LoadAd"); + } else { + // Run in automated test mode: don't fail if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + } + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + } + load_ad_future.Release(); + delete interstitial; +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdErrorNotInitialized) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + + firebase::gma::AdRequest request = GetAdRequest(); + WaitForCompletion(interstitial_ad->LoadAd(kInterstitialAdUnit, request), + "LoadAd", firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(interstitial_ad->Show(), "Show", + firebase::gma::kAdErrorCodeUninitialized); + + delete interstitial_ad; +} + +TEST_F(FirebaseGmaTest, TesInterstitialAdErrorAlreadyInitialized) { + SKIP_TEST_ON_DESKTOP; + + { + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + firebase::Future first_initialize = + interstitial_ad->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + interstitial_ad->Initialize(app_framework::GetWindowContext()); + + WaitForCompletion(first_initialize, "First Initialize 1"); + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + + first_initialize.Release(); + second_initialize.Release(); + + delete interstitial_ad; + } + + // Reverse the order of the completion waits. + { + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + firebase::Future first_initialize = + interstitial_ad->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + interstitial_ad->Initialize(app_framework::GetWindowContext()); + + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + WaitForCompletion(first_initialize, "First Initialize 1"); + + first_initialize.Release(); + second_initialize.Release(); + + delete interstitial_ad; + } +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdErrorLoadInProgress) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + WaitForCompletion( + interstitial_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the interstitial ad. + // Note potential flake: this test assumes the attempt to load an ad + // won't resolve immediately. If it does then the result may be two + // successful ad loads instead of the expected + // kAdErrorCodeLoadInProgress error. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future first_load_ad = + interstitial_ad->LoadAd(kInterstitialAdUnit, request); + firebase::Future second_load_ad = + interstitial_ad->LoadAd(kInterstitialAdUnit, request); + + WaitForCompletion(second_load_ad, "Second LoadAd", + firebase::gma::kAdErrorCodeLoadInProgress); + WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); + + const firebase::gma::AdResult* result_ptr = second_load_ad.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeLoadInProgress); + EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); + EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + first_load_ad.Release(); + second_load_ad.Release(); + delete interstitial_ad; +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdErrorBadAdUnitId) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + WaitForCompletion( + interstitial_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the interstitial ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + interstitial_ad->LoadAd(kBadAdUnit, request); + WaitForCompletion(load_ad_future, "LoadAd", + firebase::gma::kAdErrorCodeInvalidRequest); + + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeInvalidRequest); + EXPECT_FALSE(result_ptr->ad_error().message().empty()); + EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + load_ad_future.Release(); + delete interstitial_ad; +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdErrorBadExtrasClassName) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::InterstitialAd* interstitial_ad = + new firebase::gma::InterstitialAd(); + WaitForCompletion( + interstitial_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the interstitial ad. + firebase::gma::AdRequest request = GetAdRequest(); + request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); + WaitForCompletion(interstitial_ad->LoadAd(kInterstitialAdUnit, request), + "LoadAd", + firebase::gma::kAdErrorCodeAdNetworkClassLoadError); + delete interstitial_ad; +} + +// Other RewardedAd Tests. + +TEST_F(FirebaseGmaTest, TestRewardedAdLoadEmptyRequest) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + // Note: while showing an ad requires user interaction in another test, + // this test is mean as a baseline loadAd functionality test. + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the RewardedAd is initialized, load an ad. + firebase::gma::AdRequest request; + firebase::Future load_ad_future = + rewarded->LoadAd(kRewardedAdUnit, request); + + // This test behaves differently if it's running in UI mode + // (manually on a device) or in non-UI mode (via automated tests). + if (ShouldRunUITests()) { + // Run in manual mode: fail if any error occurs. + WaitForCompletion(load_ad_future, "LoadAd"); + } else { + // Run in automated test mode: don't fail if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + } + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + // In UI mode, or in non-UI mode if a NoFill error didn't occur, check that + // the ad loaded correctly. + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + } + load_ad_future.Release(); + delete rewarded; +} + +TEST_F(FirebaseGmaTest, TestRewardedAdErrorNotInitialized) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::RewardedAd* rewarded_ad = new firebase::gma::RewardedAd(); + + firebase::gma::AdRequest request = GetAdRequest(); + WaitForCompletion(rewarded_ad->LoadAd(kRewardedAdUnit, request), "LoadAd", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(rewarded_ad->Show(/*listener=*/nullptr), "Show", + firebase::gma::kAdErrorCodeUninitialized); + + delete rewarded_ad; +} + +TEST_F(FirebaseGmaTest, TesRewardedAdErrorAlreadyInitialized) { + SKIP_TEST_ON_DESKTOP; + + { + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + firebase::Future first_initialize = + rewarded->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + rewarded->Initialize(app_framework::GetWindowContext()); + + WaitForCompletionAnyResult(first_initialize, "First Initialize 1"); + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + + first_initialize.Release(); + second_initialize.Release(); + + delete rewarded; + } + + // Reverse the order of the completion waits. + { + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + firebase::Future first_initialize = + rewarded->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + rewarded->Initialize(app_framework::GetWindowContext()); + + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + WaitForCompletionAnyResult(first_initialize, "First Initialize 1"); + + first_initialize.Release(); + second_initialize.Release(); + + delete rewarded; + } +} + +TEST_F(FirebaseGmaTest, TestRewardedAdErrorLoadInProgress) { + SKIP_TEST_ON_DESKTOP; + + // TODO(@drsanta): remove when GMA whitelists CI devices. + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the rewarded ad. + // Note potential flake: this test assumes the attempt to load an ad + // won't resolve immediately. If it does then the result may be two + // successful ad loads instead of the expected + // kAdErrorCodeLoadInProgress error. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future first_load_ad = + rewarded->LoadAd(kRewardedAdUnit, request); + firebase::Future second_load_ad = + rewarded->LoadAd(kRewardedAdUnit, request); + + WaitForCompletion(second_load_ad, "Second LoadAd", + firebase::gma::kAdErrorCodeLoadInProgress); + WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); + + const firebase::gma::AdResult* result_ptr = second_load_ad.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeLoadInProgress); + EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); + EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + first_load_ad.Release(); + second_load_ad.Release(); + delete rewarded; +} + +TEST_F(FirebaseGmaTest, TestRewardedAdErrorBadAdUnitId) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the rewarded ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + rewarded->LoadAd(kBadAdUnit, request); + WaitForCompletion(load_ad_future, "LoadAd", + firebase::gma::kAdErrorCodeInvalidRequest); + + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeInvalidRequest); + EXPECT_FALSE(result_ptr->ad_error().message().empty()); + EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + load_ad_future.Release(); + delete rewarded; +} + +TEST_F(FirebaseGmaTest, TestRewardedAdErrorBadExtrasClassName) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the rewarded ad. + firebase::gma::AdRequest request = GetAdRequest(); + request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); + WaitForCompletion(rewarded->LoadAd(kRewardedAdUnit, request), "LoadAd", + firebase::gma::kAdErrorCodeAdNetworkClassLoadError); + delete rewarded; +} + +// Other NativeAd Tests + +TEST_F(FirebaseGmaTest, TestNativeAdLoadEmptyRequest) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // When the NativeAd is initialized, load an ad. + firebase::gma::AdRequest request; + + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, request); + + // Don't fail loadAd, if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); + EXPECT_FALSE( + result_ptr->response_info().mediation_adapter_class_name().empty()); + EXPECT_FALSE(result_ptr->response_info().response_id().empty()); + EXPECT_FALSE(result_ptr->response_info().ToString().empty()); + } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { + LogWarning("LoadAd returned NoFill in TestNativeAdLoadEmptyRequest"); + } + + load_ad_future.Release(); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativeRecordImpression) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Set the listener. + TestAdListener ad_listener; + native_ad->SetAdListener(&ad_listener); + + // When the NativeAd is initialized, load an ad. + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); + + // Don't fail loadAd, if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + + // Proceed verifying the RecordImpression, only when loadAd is successful. + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + firebase::Variant impression_payload = GetVariantMap(); + +#if defined(ANDROID) + // Android doesn't have a return type for this API. + WaitForCompletion(native_ad->RecordImpression(impression_payload), + "RecordImpression"); +#else // iOS + // Test Ad unit IDs are not allowlisted to record impression and the request + // is expected to be rejected by the server. iOS returns the failure. + WaitForCompletion(native_ad->RecordImpression(impression_payload), + "RecordImpression", + firebase::gma::kAdErrorCodeInvalidRequest); +#endif + + // Use an allowlisted Ad unit ID that can record an impression, to verify + // the impression count while testing locally. + EXPECT_EQ(ad_listener.num_on_ad_impression_, 0); + + firebase::Variant str_variant = + firebase::Variant::FromMutableString("test"); + WaitForCompletion(native_ad->RecordImpression(str_variant), + "RecordImpression 2", + firebase::gma::kAdErrorCodeInvalidArgument); + } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { + LogWarning("LoadAd returned NoFill in TestNativeRecordImpression"); + } + + load_ad_future.Release(); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativePerformClick) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_SIMULATOR; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Set the listener. + TestAdListener ad_listener; + native_ad->SetAdListener(&ad_listener); + + // When the NativeAd is initialized, load an ad. + firebase::Future load_ad_future = + native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); + + // Don't fail loadAd, if NoFill occurred. + WaitForCompletion( + load_ad_future, "LoadAd (ignoring NoFill error)", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + + // Proceed verifying the PerformClick, only when loadAd is successful. + if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_TRUE(result_ptr->is_successful()); + + firebase::Variant click_payload = GetVariantMap(); + + // Android and iOS doesn't have a return type for this API. + WaitForCompletion(native_ad->PerformClick(click_payload), "PerformClick"); + + // Test Ad unit IDs are not allowlisted to use PerformClick API and the + // request is expected to be rejected by the server. Use an allowlisted Ad + // unit ID to verify the ad click count while testing locally. + EXPECT_EQ(ad_listener.num_on_ad_clicked_, 0); + + firebase::Variant str_variant = + firebase::Variant::FromMutableString("test"); + WaitForCompletion(native_ad->PerformClick(str_variant), "PerformClick 2", + firebase::gma::kAdErrorCodeInvalidArgument); + } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { + LogWarning("LoadAd returned NoFill in TestNativePerformClick"); + } + + load_ad_future.Release(); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativeAdErrorNotInitialized) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + + firebase::gma::AdRequest request = GetAdRequest(); + WaitForCompletion(native_ad->LoadAd(kNativeAdUnit, request), "LoadAd", + firebase::gma::kAdErrorCodeUninitialized); + + firebase::Variant variant = firebase::Variant::EmptyMap(); + WaitForCompletion(native_ad->RecordImpression(variant), "RecordImpression", + firebase::gma::kAdErrorCodeUninitialized); + WaitForCompletion(native_ad->PerformClick(variant), "PerformClick", + firebase::gma::kAdErrorCodeUninitialized); + + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativeAdErrorAlreadyInitialized) { + SKIP_TEST_ON_DESKTOP; + + { + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + firebase::Future first_initialize = + native_ad->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + native_ad->Initialize(app_framework::GetWindowContext()); + + WaitForCompletion(first_initialize, "First Initialize 1"); + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + + first_initialize.Release(); + second_initialize.Release(); + + delete native_ad; + } + + // Reverse the order of the completion waits. + { + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + firebase::Future first_initialize = + native_ad->Initialize(app_framework::GetWindowContext()); + firebase::Future second_initialize = + native_ad->Initialize(app_framework::GetWindowContext()); + + WaitForCompletion(second_initialize, "Second Initialize 1", + firebase::gma::kAdErrorCodeAlreadyInitialized); + WaitForCompletion(first_initialize, "First Initialize 1"); + + first_initialize.Release(); + second_initialize.Release(); + + delete native_ad; + } +} + +TEST_F(FirebaseGmaTest, TestNativeAdErrorBadAdUnitId) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the native ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future load_ad_future = + native_ad->LoadAd(kBadAdUnit, request); + WaitForCompletion(load_ad_future, "LoadAd", + firebase::gma::kAdErrorCodeInvalidRequest); + + const firebase::gma::AdResult* result_ptr = load_ad_future.result(); + ASSERT_NE(result_ptr, nullptr); + EXPECT_FALSE(result_ptr->is_successful()); + EXPECT_EQ(result_ptr->ad_error().code(), + firebase::gma::kAdErrorCodeInvalidRequest); + EXPECT_FALSE(result_ptr->ad_error().message().empty()); + EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); + const firebase::gma::ResponseInfo response_info = + result_ptr->ad_error().response_info(); + EXPECT_TRUE(response_info.adapter_responses().empty()); + + load_ad_future.Release(); + delete native_ad; +} + +TEST_F(FirebaseGmaTest, TestNativeAdErrorBadExtrasClassName) { + SKIP_TEST_ON_DESKTOP; + + firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); + WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), + "Initialize"); + + // Load the native ad. + firebase::gma::AdRequest request = GetAdRequest(); + request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); + WaitForCompletion(native_ad->LoadAd(kNativeAdUnit, request), "LoadAd", + firebase::gma::kAdErrorCodeAdNetworkClassLoadError); + delete native_ad; +} + +// Stress tests. These take a while so run them near the end. +TEST_F(FirebaseGmaTest, TestAdViewStress) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_EMULATOR; + + // TODO(@drsanta): remove when GMA whitelists CI devices + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; + + for (int i = 0; i < 10; ++i) { + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "TestAdViewStress Initialize"); + + // Load the AdView ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future future = ad_view->LoadAd(request); + WaitForCompletion( + future, "TestAdViewStress LoadAd", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + // Stress tests may exhaust the ad pool. If so, loadAd will return + // kAdErrorCodeNoFill. + if (future.error() == firebase::gma::kAdErrorCodeNone) { + EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); + EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); + } + WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); + delete ad_view; + } +} + +TEST_F(FirebaseGmaTest, TestInterstitialAdStress) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_EMULATOR; + + // TODO(@drsanta): remove when GMA whitelists CI devices + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; + + for (int i = 0; i < 10; ++i) { + firebase::gma::InterstitialAd* interstitial = + new firebase::gma::InterstitialAd(); + + WaitForCompletion( + interstitial->Initialize(app_framework::GetWindowContext()), + "TestInterstitialAdStress Initialize"); + + // When the InterstitialAd is initialized, load an ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future future = + interstitial->LoadAd(kInterstitialAdUnit, request); + WaitForCompletion( + future, "TestInterstitialAdStress LoadAd", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + // Stress tests may exhaust the ad pool. If so, loadAd will return + // kAdErrorCodeNoFill. + delete interstitial; + } +} + +TEST_F(FirebaseGmaTest, TestRewardedAdStress) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_EMULATOR; + + // TODO(@drsanta): remove when GMA whitelists CI devices. + TEST_REQUIRES_USER_INTERACTION_ON_IOS; + TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; + + for (int i = 0; i < 10; ++i) { + firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); + + WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), + "TestRewardedAdStress Initialize"); + + // When the RewardedAd is initialized, load an ad. + firebase::gma::AdRequest request = GetAdRequest(); + firebase::Future future = + rewarded->LoadAd(kRewardedAdUnit, request); + WaitForCompletion( + future, "TestRewardedAdStress LoadAd", + {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); + // Stress tests may exhaust the ad pool. If so, loadAd will return + // kAdErrorCodeNoFill. + delete rewarded; + } +} + +#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +// Test runs & compiles for phones only. + +struct ThreadArgs { + firebase::gma::AdView* ad_view; + sem_t* semaphore; +}; + +static void* DeleteAdViewOnSignal(void* args) { + ThreadArgs* thread_args = static_cast(args); + sem_wait(thread_args->semaphore); + delete thread_args->ad_view; + return nullptr; +} + +TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_MOBILE; // TODO(b/172832275): This test is temporarily + // disabled on all platforms due to flakiness + // on Android. Once it's fixed, this test should + // be re-enabled on mobile. + + const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); + + for (int i = 0; i < 5; ++i) { + firebase::gma::AdView* ad_view = new firebase::gma::AdView(); + WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), + kBannerAdUnit, banner_ad_size), + "Initialize"); + sem_t semaphore; + sem_init(&semaphore, 0, 1); + + ThreadArgs args = {ad_view, &semaphore}; + + pthread_t t1; + int err = pthread_create(&t1, nullptr, &DeleteAdViewOnSignal, &args); + EXPECT_EQ(err, 0); + + ad_view->Destroy(); + sem_post(&semaphore); + + // Blocks until DeleteAdViewOnSignal function is done. + void* result = nullptr; + err = pthread_join(t1, &result); + + EXPECT_EQ(err, 0); + EXPECT_EQ(result, nullptr); + + sem_destroy(&semaphore); + } +} +#endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && + // TARGET_OS_IPHONE) + +class FirebaseGmaUmpTest : public FirebaseGmaTest { + public: + FirebaseGmaUmpTest() : consent_info_(nullptr) {} + + // Whether to call ConsentInfo::Reset() upon initialization, which + // resets UMP's consent state to as if the app was first installed. + enum ResetOption { kReset, kNoReset }; + + void InitializeUmp(ResetOption reset = kReset); + void TerminateUmp(ResetOption reset = kReset); + + void SetUp() override; + void TearDown() override; + + protected: + firebase::gma::ump::ConsentInfo* consent_info_; +}; + +void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) { + using firebase::gma::ump::ConsentInfo; + firebase::InitResult result; + consent_info_ = ConsentInfo::GetInstance(*shared_app_, &result); + + EXPECT_NE(consent_info_, nullptr); + EXPECT_EQ(result, firebase::kInitResultSuccess); + + if (consent_info_ != nullptr && reset == kReset) { + consent_info_->Reset(); + } +} + +void FirebaseGmaUmpTest::TerminateUmp(ResetOption reset) { + if (consent_info_) { + if (reset == kReset) { + consent_info_->Reset(); + } + delete consent_info_; + consent_info_ = nullptr; + } +} + +void FirebaseGmaUmpTest::SetUp() { + FirebaseGmaTest::SetUp(); + InitializeUmp(); + ASSERT_NE(consent_info_, nullptr); +} + +void FirebaseGmaUmpTest::TearDown() { + TerminateUmp(); + FirebaseGmaTest::TearDown(); +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpInitialization) { + // Initialize handled automatically in test setup. + EXPECT_NE(consent_info_, nullptr); + // Terminate handled automatically in test teardown. +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpDefaultsToUnknownStatus) { + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusUnknown); + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnknown); + EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); + EXPECT_FALSE(consent_info_->CanRequestAds()); +} + +// Tests for User Messaging Platform +TEST_F(FirebaseGmaUmpTest, TestUmpGetInstanceIsAlwaysEqual) { + using firebase::gma::ump::ConsentInfo; + + EXPECT_NE(consent_info_, nullptr); + + // Ensure that GetInstance() with any options is always equal. + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance()); + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance(*shared_app_)); + +#if defined(ANDROID) + EXPECT_EQ(consent_info_, + ConsentInfo::GetInstance(app_framework::GetJniEnv(), + app_framework::GetActivity())); + + firebase::App* second_app = firebase::App::Create( + firebase::AppOptions(), "2ndApp", app_framework::GetJniEnv(), + app_framework::GetActivity()); +#else + firebase::App* second_app = + firebase::App::Create(firebase::AppOptions(), "2ndApp"); +#endif // defined(ANDROID) + + EXPECT_EQ(consent_info_, ConsentInfo::GetInstance(*second_app)); + + delete second_app; +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + EXPECT_TRUE(future == consent_info_->RequestConsentInfoUpdateLastResult()); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + EXPECT_NE(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusUnknown); + EXPECT_NE(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusUnknown); + EXPECT_NE(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + EXPECT_THAT(consent_info_->GetConsentStatus(), + AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), + Eq(firebase::gma::ump::kConsentStatusRequired))); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + // Load the form. Run this step with retry in case of network timeout. + WaitForCompletion( + RunWithRetry([&]() { return consent_info_->LoadConsentForm(); }), + "LoadConsentForm", + {firebase::gma::ump::kConsentFormSuccess, + firebase::gma::ump::kConsentFormErrorTimeout}); + + firebase::Future future = consent_info_->LoadConsentFormLastResult(); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + if (future.error() == firebase::gma::ump::kConsentFormErrorTimeout) { + LogWarning("Timed out after multiple tries, but passing anyway."); + } +} + +TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { + TEST_REQUIRES_USER_INTERACTION; + + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm"); + + EXPECT_EQ(consent_info_->GetConsentFormStatus(), + firebase::gma::ump::kConsentFormStatusAvailable); + + firebase::Future future = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + + EXPECT_TRUE(future == consent_info_->ShowConsentFormLastResult()); + + WaitForCompletion(future, "ShowConsentForm"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnderAgeOfConsent) { + SKIP_TEST_ON_IOS_SIMULATOR; + + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = true; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + firebase::Future load_future = consent_info_->LoadConsentForm(); + WaitForCompletion(load_future, "LoadConsentForm", + {firebase::gma::ump::kConsentFormErrorUnavailable, + firebase::gma::ump::kConsentFormErrorTimeout, + firebase::gma::ump::kConsentFormSuccess}); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + if (consent_info_->GetConsentStatus() != + firebase::gma::ump::kConsentStatusRequired) { + WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm", + firebase::gma::ump::kConsentFormErrorUnavailable); + } +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future = + consent_info_->RequestConsentInfoUpdate(params); + + WaitForCompletion(future, "RequestConsentInfoUpdate", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + // Retry only network errors. + EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + + FLAKY_TEST_SECTION_END(); + + EXPECT_THAT(consent_info_->GetConsentStatus(), + AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), + Eq(firebase::gma::ump::kConsentStatusRequired))); + + if (consent_info_->GetConsentStatus() == + firebase::gma::ump::kConsentStatusNotRequired || + ShouldRunUITests()) { + // If ConsentStatus is Required, we only want to do this next part if UI + // interaction is allowed, as it will show a consent form which won't work + // in automated testing. + firebase::Future future = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + + EXPECT_TRUE(future == + consent_info_->LoadAndShowConsentFormIfRequiredLastResult()); + + WaitForCompletion(future, "LoadAndShowConsentFormIfRequired"); + } +} + +TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + firebase::Future future = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + + EXPECT_TRUE(future == + consent_info_->LoadAndShowConsentFormIfRequiredLastResult()); + + WaitForCompletion(future, "LoadAndShowConsentFormIfRequired"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + using firebase::gma::ump::PrivacyOptionsRequirementStatus; + + TEST_REQUIRES_USER_INTERACTION; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_FALSE(consent_info_->CanRequestAds()); + + WaitForCompletion(consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()), + "LoadAndShowConsentFormIfRequired"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusObtained); + + EXPECT_TRUE(consent_info_->CanRequestAds()) << "After consent obtained"; + + LogInfo( + "******** On the Privacy Options screen that is about to appear, please " + "select DO NOT CONSENT."); + + ProcessEvents(5000); + + EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), + firebase::gma::ump::kPrivacyOptionsRequirementStatusRequired); + + firebase::Future future = consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); + + EXPECT_TRUE(future == consent_info_->ShowPrivacyOptionsFormLastResult()); + + WaitForCompletion(future, "ShowPrivacyOptionsForm"); +} + +TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_THAT(consent_info_->GetConsentStatus(), + AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), + Eq(firebase::gma::ump::kConsentStatusRequired))); + + if (consent_info_->GetConsentStatus() == + firebase::gma::ump::kConsentStatusNotRequired) { + EXPECT_TRUE(consent_info_->CanRequestAds()); + } +} + +TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { + using firebase::gma::ump::ConsentDebugSettings; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + EXPECT_EQ(consent_info_->GetConsentStatus(), + firebase::gma::ump::kConsentStatusRequired); + + EXPECT_FALSE(consent_info_->CanRequestAds()); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpCleanupWithDelay) { + // Ensure that if ConsentInfo is deleted after a delay, Futures are + // properly invalidated. + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future_request = + consent_info_->RequestConsentInfoUpdate(params); + firebase::Future future_load = consent_info_->LoadConsentForm(); + firebase::Future future_show = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + firebase::Future future_load_and_show = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + firebase::Future future_privacy = consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); + + ProcessEvents(5000); + + TerminateUmp(kNoReset); + + EXPECT_EQ(future_request.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_load.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_show.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_load_and_show.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_privacy.status(), firebase::kFutureStatusInvalid); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpCleanupRaceCondition) { + // Ensure that if ConsentInfo is deleted immediately, operations + // (and their Futures) are properly invalidated. + + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future_request = + consent_info_->RequestConsentInfoUpdate(params); + firebase::Future future_load = consent_info_->LoadConsentForm(); + firebase::Future future_show = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + firebase::Future future_load_and_show = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + firebase::Future future_privacy = consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); + + TerminateUmp(kNoReset); + + EXPECT_EQ(future_request.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_load.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_show.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_load_and_show.status(), firebase::kFutureStatusInvalid); + EXPECT_EQ(future_privacy.status(), firebase::kFutureStatusInvalid); + + ProcessEvents(5000); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpCallbacksOnWrongInstance) { + // Ensure that if ConsentInfo is deleted and then recreated, stale + // callbacks don't call into the new instance and cause crashes. + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + LogDebug("RequestConsentInfoUpdate"); + consent_info_->RequestConsentInfoUpdate(params).OnCompletion( + [](const firebase::Future&) { + LogDebug("RequestConsentInfoUpdate done"); + }); + LogDebug("LoadConsentForm"); + consent_info_->LoadConsentForm().OnCompletion( + [](const firebase::Future&) { LogDebug("LoadConsentForm done"); }); + // In automated tests, only check RequestConsentInfoUpdate and LoadConsentForm + // as the rest may show UI. + if (ShouldRunUITests()) { + LogDebug("ShowConsentForm"); + consent_info_->ShowConsentForm(app_framework::GetWindowController()) + .OnCompletion([](const firebase::Future&) { + LogDebug("ShowConsentForm done"); + }); + LogDebug("LoadAndShowConsentFormIfRequired"); + consent_info_ + ->LoadAndShowConsentFormIfRequired(app_framework::GetWindowController()) + .OnCompletion([](const firebase::Future&) { + LogDebug("LoadAndShowConsentFormIfRequired done"); + }); + LogDebug("ShowPrivacyOptionsForm"); + consent_info_->ShowPrivacyOptionsForm(app_framework::GetWindowController()) + .OnCompletion([](const firebase::Future&) { + LogDebug("ShowPrivacyOptionsForm done"); + }); + } + + LogDebug("Terminate"); + TerminateUmp(kNoReset); + + LogDebug("Initialize"); + InitializeUmp(kNoReset); + + // Give the operations time to complete. + LogDebug("Wait"); + ProcessEvents(5000); + + LogDebug("Done"); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgress) { + SKIP_TEST_ON_DESKTOP; + SKIP_TEST_ON_IOS_SIMULATOR; // LoadAndShowConsentFormIfRequired + // is too quick on simulator. + + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + // Check that all of the UMP operations properly return an OperationInProgress + // error if called more than once at the same time. + + // This depends on timing, so it's inherently flaky. + FLAKY_TEST_SECTION_BEGIN(); + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyNonEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future_request_1 = + consent_info_->RequestConsentInfoUpdate(params); + firebase::Future future_request_2 = + consent_info_->RequestConsentInfoUpdate(params); + WaitForCompletion( + future_request_2, "RequestConsentInfoUpdate second", + firebase::gma::ump::kConsentRequestErrorOperationInProgress); + WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first", + {firebase::gma::ump::kConsentRequestSuccess, + firebase::gma::ump::kConsentRequestErrorNetwork}); + + consent_info_->Reset(); + + FLAKY_TEST_SECTION_END(); +} + +TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { + SKIP_TEST_ON_DESKTOP; + TEST_REQUIRES_USER_INTERACTION; + + using firebase::gma::ump::ConsentFormStatus; + using firebase::gma::ump::ConsentRequestParameters; + using firebase::gma::ump::ConsentStatus; + + // Check that all of the UMP operations properly return an OperationInProgress + // error if called more than once at the same time. This test include methods + // with UI interaction. + + ConsentRequestParameters params; + params.tag_for_under_age_of_consent = false; + params.debug_settings.debug_geography = + firebase::gma::ump::kConsentDebugGeographyEEA; + params.debug_settings.debug_device_ids = kTestDeviceIDs; + params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); + + firebase::Future future_request_1 = + consent_info_->RequestConsentInfoUpdate(params); + firebase::Future future_request_2 = + consent_info_->RequestConsentInfoUpdate(params); + WaitForCompletion( + future_request_2, "RequestConsentInfoUpdate second", + firebase::gma::ump::kConsentRequestErrorOperationInProgress); + WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first"); + + firebase::Future future_load_1 = consent_info_->LoadConsentForm(); + firebase::Future future_load_2 = consent_info_->LoadConsentForm(); + WaitForCompletion(future_load_2, "LoadConsentForm second", + firebase::gma::ump::kConsentFormErrorOperationInProgress); + WaitForCompletion(future_load_1, "LoadConsentForm first"); + + firebase::Future future_show_1 = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + firebase::Future future_show_2 = + consent_info_->ShowConsentForm(app_framework::GetWindowController()); + WaitForCompletion(future_show_2, "ShowConsentForm second", + firebase::gma::ump::kConsentFormErrorOperationInProgress); + WaitForCompletion(future_show_1, "ShowConsentForm first"); + + firebase::Future future_privacy_1 = + consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); + firebase::Future future_privacy_2 = + consent_info_->ShowPrivacyOptionsForm( + app_framework::GetWindowController()); + WaitForCompletion(future_privacy_2, "ShowPrivacyOptionsForm second", + firebase::gma::ump::kConsentFormErrorOperationInProgress); + WaitForCompletion(future_privacy_1, "ShowPrivacyOptionsForm first"); + + consent_info_->Reset(); + // Request again so we can test LoadAndShowConsentFormIfRequired. + WaitForCompletion(consent_info_->RequestConsentInfoUpdate(params), + "RequestConsentInfoUpdate"); + + firebase::Future future_load_and_show_1 = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + firebase::Future future_load_and_show_2 = + consent_info_->LoadAndShowConsentFormIfRequired( + app_framework::GetWindowController()); + WaitForCompletion(future_load_and_show_2, + "LoadAndShowConsentFormIfRequired second", + firebase::gma::ump::kConsentFormErrorOperationInProgress); + WaitForCompletion(future_load_and_show_1, + "LoadAndShowConsentFormIfRequired first"); +} + +} // namespace firebase_testapp_automated diff --git a/ump/src/android/ump/consent_info_internal_android.cc b/ump/src/android/ump/consent_info_internal_android.cc new file mode 100644 index 0000000000..903b7126cd --- /dev/null +++ b/ump/src/android/ump/consent_info_internal_android.cc @@ -0,0 +1,668 @@ +/* + * 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. + */ + +#include "gma/src/android/ump/consent_info_internal_android.h" + +#include + +#include +#include + +#include "app/src/assert.h" +#include "app/src/thread.h" +#include "app/src/util_android.h" +#include "firebase/internal/common.h" +#include "gma/gma_resources.h" +#include "gma/src/android/gma_android.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +ConsentInfoInternalAndroid* ConsentInfoInternalAndroid::s_instance = nullptr; +firebase::Mutex ConsentInfoInternalAndroid::s_instance_mutex; + +// clang-format off +#define CONSENTINFOHELPER_METHODS(X) \ + X(Constructor, "", "(JLandroid/app/Activity;)V"), \ + X(GetConsentStatus, "getConsentStatus", "()I"), \ + X(RequestConsentInfoUpdate, "requestConsentInfoUpdate", \ + "(JZILjava/util/ArrayList;)V"), \ + X(LoadConsentForm, "loadConsentForm", "(J)V"), \ + X(ShowConsentForm, "showConsentForm", "(JLandroid/app/Activity;)Z"), \ + X(LoadAndShowConsentFormIfRequired, "loadAndShowConsentFormIfRequired", \ + "(JLandroid/app/Activity;)V"), \ + X(GetPrivacyOptionsRequirementStatus, "getPrivacyOptionsRequirementStatus", \ + "()I"), \ + X(ShowPrivacyOptionsForm, "showPrivacyOptionsForm", \ + "(JLandroid/app/Activity;)V"), \ + X(Reset, "reset", "()V"), \ + X(CanRequestAds, "canRequestAds", "()Z"), \ + X(IsConsentFormAvailable, "isConsentFormAvailable", "()Z"), \ + X(Disconnect, "disconnect", "()V") +// clang-format on + +// clang-format off +#define CONSENTINFOHELPER_FIELDS(X) \ + X(PrivacyOptionsRequirementUnknown, \ + "PRIVACY_OPTIONS_REQUIREMENT_UNKNOWN", "I", util::kFieldTypeStatic), \ + X(PrivacyOptionsRequirementRequired, \ + "PRIVACY_OPTIONS_REQUIREMENT_REQUIRED", "I", util::kFieldTypeStatic), \ + X(PrivacyOptionsRequirementNotRequired, \ + "PRIVACY_OPTIONS_REQUIREMENT_NOT_REQUIRED", "I", util::kFieldTypeStatic), \ + X(FunctionRequestConsentInfoUpdate, \ + "FUNCTION_REQUEST_CONSENT_INFO_UPDATE", "I", util::kFieldTypeStatic), \ + X(FunctionLoadConsentForm, \ + "FUNCTION_LOAD_CONSENT_FORM", "I", util::kFieldTypeStatic), \ + X(FunctionShowConsentForm, \ + "FUNCTION_SHOW_CONSENT_FORM", "I", util::kFieldTypeStatic), \ + X(FunctionLoadAndShowConsentFormIfRequired, \ + "FUNCTION_LOAD_AND_SHOW_CONSENT_FORM_IF_REQUIRED", \ + "I", util::kFieldTypeStatic), \ + X(FunctionShowPrivacyOptionsForm, \ + "FUNCTION_SHOW_PRIVACY_OPTIONS_FORM", "I", util::kFieldTypeStatic), \ + X(FunctionCount, "FUNCTION_COUNT", "I", util::kFieldTypeStatic) +// clang-format on + +METHOD_LOOKUP_DECLARATION(consent_info_helper, CONSENTINFOHELPER_METHODS, + CONSENTINFOHELPER_FIELDS); + +METHOD_LOOKUP_DEFINITION( + consent_info_helper, + "com/google/firebase/gma/internal/cpp/ConsentInfoHelper", + CONSENTINFOHELPER_METHODS, CONSENTINFOHELPER_FIELDS); + +// clang-format off +#define CONSENTINFORMATION_CONSENTSTATUS_FIELDS(X) \ + X(Unknown, "UNKNOWN", "I", util::kFieldTypeStatic), \ + X(NotRequired, "NOT_REQUIRED", "I", util::kFieldTypeStatic), \ + X(Required, "REQUIRED", "I", util::kFieldTypeStatic), \ + X(Obtained, "OBTAINED", "I", util::kFieldTypeStatic) +// clang-format on + +METHOD_LOOKUP_DECLARATION(consentinformation_consentstatus, METHOD_LOOKUP_NONE, + CONSENTINFORMATION_CONSENTSTATUS_FIELDS); +METHOD_LOOKUP_DEFINITION( + consentinformation_consentstatus, + PROGUARD_KEEP_CLASS + "com/google/android/ump/ConsentInformation$ConsentStatus", + METHOD_LOOKUP_NONE, CONSENTINFORMATION_CONSENTSTATUS_FIELDS); + +// clang-format off +#define FORMERROR_ERRORCODE_FIELDS(X) \ + X(InternalError, "INTERNAL_ERROR", "I", util::kFieldTypeStatic), \ + X(InternetError, "INTERNET_ERROR", "I", util::kFieldTypeStatic), \ + X(InvalidOperation, "INVALID_OPERATION", "I", util::kFieldTypeStatic), \ + X(TimeOut, "TIME_OUT", "I", util::kFieldTypeStatic) +// clang-format on + +METHOD_LOOKUP_DECLARATION(formerror_errorcode, METHOD_LOOKUP_NONE, + FORMERROR_ERRORCODE_FIELDS); +METHOD_LOOKUP_DEFINITION(formerror_errorcode, + PROGUARD_KEEP_CLASS + "com/google/android/ump/FormError$ErrorCode", + METHOD_LOOKUP_NONE, FORMERROR_ERRORCODE_FIELDS); + +// clang-format off +#define CONSENTDEBUGSETTINGS_DEBUGGEOGRAPHY_FIELDS(X) \ + X(Disabled, "DEBUG_GEOGRAPHY_DISABLED", "I", util::kFieldTypeStatic), \ + X(EEA, "DEBUG_GEOGRAPHY_EEA", "I", util::kFieldTypeStatic), \ + X(NotEEA, "DEBUG_GEOGRAPHY_NOT_EEA", "I", util::kFieldTypeStatic) +// clang-format on + +METHOD_LOOKUP_DECLARATION(consentdebugsettings_debuggeography, + METHOD_LOOKUP_NONE, + CONSENTDEBUGSETTINGS_DEBUGGEOGRAPHY_FIELDS); +METHOD_LOOKUP_DEFINITION( + consentdebugsettings_debuggeography, + PROGUARD_KEEP_CLASS + "com/google/android/ump/ConsentDebugSettings$DebugGeography", + METHOD_LOOKUP_NONE, CONSENTDEBUGSETTINGS_DEBUGGEOGRAPHY_FIELDS); + +// This explicitly implements the constructor for the outer class, +// ConsentInfoInternal. +ConsentInfoInternal* ConsentInfoInternal::CreateInstance(JNIEnv* jni_env, + jobject activity) { + ConsentInfoInternalAndroid* ptr = + new ConsentInfoInternalAndroid(jni_env, activity); + if (!ptr->valid()) { + delete ptr; + return nullptr; + } + return ptr; +} + +static void ReleaseClasses(JNIEnv* env) { + consent_info_helper::ReleaseClass(env); + consentinformation_consentstatus::ReleaseClass(env); + formerror_errorcode::ReleaseClass(env); + consentdebugsettings_debuggeography::ReleaseClass(env); +} + +ConsentInfoInternalAndroid::~ConsentInfoInternalAndroid() { + JNIEnv* env = GetJNIEnv(); + env->CallVoidMethod(helper_, consent_info_helper::GetMethodId( + consent_info_helper::kDisconnect)); + + MutexLock lock(s_instance_mutex); + s_instance = nullptr; + + env->DeleteGlobalRef(helper_); + helper_ = nullptr; + + ReleaseClasses(env); + util::Terminate(env); + + env->DeleteGlobalRef(activity_); + activity_ = nullptr; + java_vm_ = nullptr; +} + +// clang-format off +#define ENUM_VALUE(class_namespace, field_name) \ + env->GetStaticIntField(class_namespace::GetClass(), \ + class_namespace::GetFieldId(class_namespace::k##field_name)) +// clang-format on + +void ConsentInfoInternalAndroid::CacheEnumValues(JNIEnv* env) { + // Cache enum values when the class loads, to avoid JNI lookups during + // callbacks later on when converting enums between Android and C++ values. + enums_.consentstatus_unknown = + ENUM_VALUE(consentinformation_consentstatus, Unknown); + enums_.consentstatus_required = + ENUM_VALUE(consentinformation_consentstatus, Required); + enums_.consentstatus_not_required = + ENUM_VALUE(consentinformation_consentstatus, NotRequired); + enums_.consentstatus_obtained = + ENUM_VALUE(consentinformation_consentstatus, Obtained); + + enums_.debug_geography_disabled = + ENUM_VALUE(consentdebugsettings_debuggeography, Disabled); + enums_.debug_geography_eea = + ENUM_VALUE(consentdebugsettings_debuggeography, EEA); + enums_.debug_geography_not_eea = + ENUM_VALUE(consentdebugsettings_debuggeography, NotEEA); + + enums_.formerror_success = 0; + enums_.formerror_internal = ENUM_VALUE(formerror_errorcode, InternalError); + enums_.formerror_network = ENUM_VALUE(formerror_errorcode, InternetError); + enums_.formerror_invalid_operation = + ENUM_VALUE(formerror_errorcode, InvalidOperation); + enums_.formerror_timeout = ENUM_VALUE(formerror_errorcode, TimeOut); + + enums_.privacy_options_requirement_unknown = + ENUM_VALUE(consent_info_helper, PrivacyOptionsRequirementUnknown); + enums_.privacy_options_requirement_required = + ENUM_VALUE(consent_info_helper, PrivacyOptionsRequirementRequired); + enums_.privacy_options_requirement_not_required = + ENUM_VALUE(consent_info_helper, PrivacyOptionsRequirementNotRequired); + + enums_.function_request_consent_info_update = + ENUM_VALUE(consent_info_helper, FunctionRequestConsentInfoUpdate); + enums_.function_load_consent_form = + ENUM_VALUE(consent_info_helper, FunctionLoadConsentForm); + enums_.function_show_consent_form = + ENUM_VALUE(consent_info_helper, FunctionShowConsentForm); + enums_.function_load_and_show_consent_form_if_required = + ENUM_VALUE(consent_info_helper, FunctionLoadAndShowConsentFormIfRequired); + enums_.function_show_privacy_options_form = + ENUM_VALUE(consent_info_helper, FunctionShowPrivacyOptionsForm); + enums_.function_count = ENUM_VALUE(consent_info_helper, FunctionCount); +} + +void ConsentInfoInternalAndroid::JNI_ConsentInfoHelper_completeFuture( + JNIEnv* env, jclass clazz, jint future_fn, jlong consent_info_internal_ptr, + jlong future_handle, jint error_code, jobject error_message_obj) { + MutexLock lock(s_instance_mutex); + if (consent_info_internal_ptr == 0 || s_instance == nullptr) { + // Calling this with a null pointer, or if there is no active + // instance, is a no-op, so just return. + return; + } + ConsentInfoInternalAndroid* instance = + reinterpret_cast(consent_info_internal_ptr); + if (s_instance != instance) { + // If the instance we were called with does not match the current + // instance, a bad race condition has occurred (whereby while waiting for + // the operation to complete, ConsentInfo was deleted and then recreated). + // In that case, fully ignore this callback. + return; + } + std::string error_message = + error_message_obj ? util::JniStringToString(env, error_message_obj) : ""; + instance->CompleteFutureFromJniCallback( + env, future_fn, static_cast(future_handle), + static_cast(error_code), + error_message.length() > 0 ? error_message.c_str() : nullptr); +} + +ConsentInfoInternalAndroid::ConsentInfoInternalAndroid(JNIEnv* env, + jobject activity) + : java_vm_(nullptr), + activity_(nullptr), + helper_(nullptr), + has_requested_consent_info_update_(false) { + MutexLock lock(s_instance_mutex); + FIREBASE_ASSERT(s_instance == nullptr); + s_instance = this; + + util::Initialize(env, activity); + env->GetJavaVM(&java_vm_); + + // Between this and GMA, we only want to load these files once. + { + MutexLock lock( + ::firebase::gma::internal::g_cached_gma_embedded_files_mutex); + if (::firebase::gma::internal::g_cached_gma_embedded_files == nullptr) { + ::firebase::gma::internal::g_cached_gma_embedded_files = + new std::vector(); + *::firebase::gma::internal::g_cached_gma_embedded_files = + util::CacheEmbeddedFiles(env, activity, + firebase::internal::EmbeddedFile::ToVector( + firebase_gma::gma_resources_filename, + firebase_gma::gma_resources_data, + firebase_gma::gma_resources_size)); + } + } + const std::vector& embedded_files = + *::firebase::gma::internal::g_cached_gma_embedded_files; + + if (!(consent_info_helper::CacheClassFromFiles(env, activity, + &embedded_files) != nullptr && + consent_info_helper::CacheMethodIds(env, activity) && + consent_info_helper::CacheFieldIds(env, activity) && + consentinformation_consentstatus::CacheFieldIds(env, activity) && + formerror_errorcode::CacheFieldIds(env, activity) && + consentdebugsettings_debuggeography::CacheFieldIds(env, activity))) { + ReleaseClasses(env); + util::Terminate(env); + return; + } + static const JNINativeMethod kConsentInfoHelperNativeMethods[] = { + {"completeFuture", "(IJJILjava/lang/String;)V", + reinterpret_cast(&JNI_ConsentInfoHelper_completeFuture)}}; + if (!consent_info_helper::RegisterNatives( + env, kConsentInfoHelperNativeMethods, + FIREBASE_ARRAYSIZE(kConsentInfoHelperNativeMethods))) { + util::CheckAndClearJniExceptions(env); + ReleaseClasses(env); + util::Terminate(env); + return; + } + util::CheckAndClearJniExceptions(env); + jobject helper_ref = env->NewObject( + consent_info_helper::GetClass(), + consent_info_helper::GetMethodId(consent_info_helper::kConstructor), + reinterpret_cast(this), activity); + util::CheckAndClearJniExceptions(env); + if (!helper_ref) { + ReleaseClasses(env); + util::Terminate(env); + return; + } + + helper_ = env->NewGlobalRef(helper_ref); + FIREBASE_ASSERT(helper_); + env->DeleteLocalRef(helper_ref); + + activity_ = env->NewGlobalRef(activity); + + util::CheckAndClearJniExceptions(env); + + CacheEnumValues(env); + + util::CheckAndClearJniExceptions(env); +} + +ConsentStatus ConsentInfoInternalAndroid::CppConsentStatusFromAndroid( + jint status) { + if (status == enums().consentstatus_unknown) return kConsentStatusUnknown; + if (status == enums().consentstatus_required) return kConsentStatusRequired; + if (status == enums().consentstatus_not_required) + return kConsentStatusNotRequired; + if (status == enums().consentstatus_obtained) return kConsentStatusObtained; + LogWarning("GMA: Unknown ConsentStatus returned by UMP Android SDK: %d", + (int)status); + return kConsentStatusUnknown; +} + +PrivacyOptionsRequirementStatus +ConsentInfoInternalAndroid::CppPrivacyOptionsRequirementStatusFromAndroid( + jint status) { + if (status == enums().privacy_options_requirement_unknown) + return kPrivacyOptionsRequirementStatusUnknown; + if (status == enums().privacy_options_requirement_required) + return kPrivacyOptionsRequirementStatusRequired; + if (status == enums().privacy_options_requirement_not_required) + return kPrivacyOptionsRequirementStatusNotRequired; + LogWarning( + "GMA: Unknown PrivacyOptionsRequirementStatus returned by UMP Android " + "SDK: %d", + (int)status); + return kPrivacyOptionsRequirementStatusUnknown; +} + +jint ConsentInfoInternalAndroid::AndroidDebugGeographyFromCppDebugGeography( + ConsentDebugGeography geo) { + switch (geo) { + case kConsentDebugGeographyDisabled: + return enums().debug_geography_disabled; + case kConsentDebugGeographyEEA: + return enums().debug_geography_eea; + case kConsentDebugGeographyNonEEA: + return enums().debug_geography_not_eea; + default: + return enums().debug_geography_disabled; + } +} + +// Android uses FormError to report request errors as well. +ConsentRequestError +ConsentInfoInternalAndroid::CppConsentRequestErrorFromAndroidFormError( + jint error, const char* message) { + if (error == enums().formerror_success) return kConsentRequestSuccess; + if (error == enums().formerror_internal) return kConsentRequestErrorInternal; + if (error == enums().formerror_network) return kConsentRequestErrorNetwork; + if (error == enums().formerror_invalid_operation) { + // Error strings taken directly from the UMP Android SDK. + if (message && strcasestr(message, "misconfiguration") != nullptr) + return kConsentRequestErrorMisconfiguration; + else if (message && + strcasestr(message, "requires a valid application ID") != nullptr) + return kConsentRequestErrorInvalidAppId; + else + return kConsentRequestErrorInvalidOperation; + } + LogWarning("GMA: Unknown RequestError returned by UMP Android SDK: %d (%s)", + (int)error, message ? message : ""); + return kConsentRequestErrorUnknown; +} + +ConsentFormError +ConsentInfoInternalAndroid::CppConsentFormErrorFromAndroidFormError( + jint error, const char* message) { + if (error == enums().formerror_success) return kConsentFormSuccess; + if (error == enums().formerror_internal) return kConsentFormErrorInternal; + if (error == enums().formerror_timeout) return kConsentFormErrorTimeout; + if (error == enums().formerror_invalid_operation) { + // Error strings taken directly from the UMP Android SDK. + if (message && strcasestr(message, "no available form") != nullptr) + return kConsentFormErrorUnavailable; + else if (message && strcasestr(message, "form is not required") != nullptr) + return kConsentFormErrorUnavailable; + else if (message && + strcasestr(message, "can only be invoked once") != nullptr) + return kConsentFormErrorAlreadyUsed; + else + return kConsentFormErrorInvalidOperation; + } + LogWarning("GMA: Unknown RequestError returned by UMP Android SDK: %d (%s)", + (int)error, message ? message : ""); + return kConsentFormErrorUnknown; +} + +Future ConsentInfoInternalAndroid::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + if (RequestConsentInfoUpdateLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentRequestErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnRequestConsentInfoUpdate); + JNIEnv* env = GetJNIEnv(); + + jlong future_handle = static_cast(handle.get().id()); + jboolean tag_for_under_age_of_consent = + params.tag_for_under_age_of_consent ? JNI_TRUE : JNI_FALSE; + jint debug_geography = AndroidDebugGeographyFromCppDebugGeography( + params.debug_settings.debug_geography); + jobject debug_device_ids_list = + util::StdVectorToJavaList(env, params.debug_settings.debug_device_ids); + env->CallVoidMethod(helper_, + consent_info_helper::GetMethodId( + consent_info_helper::kRequestConsentInfoUpdate), + future_handle, tag_for_under_age_of_consent, + debug_geography, debug_device_ids_list); + + if (env->ExceptionCheck()) { + std::string exception_message = util::GetAndClearExceptionMessage(env); + CompleteFuture(handle, kConsentRequestErrorInternal, + exception_message.c_str()); + } else { + has_requested_consent_info_update_ = true; + } + env->DeleteLocalRef(debug_device_ids_list); + + return MakeFuture(futures(), handle); +} + +ConsentStatus ConsentInfoInternalAndroid::GetConsentStatus() { + if (!valid()) { + return kConsentStatusUnknown; + } + JNIEnv* env = GetJNIEnv(); + jint result = env->CallIntMethod( + helper_, + consent_info_helper::GetMethodId(consent_info_helper::kGetConsentStatus)); + if (env->ExceptionCheck()) { + util::CheckAndClearJniExceptions(env); + return kConsentStatusUnknown; + } + return CppConsentStatusFromAndroid(result); +} + +ConsentFormStatus ConsentInfoInternalAndroid::GetConsentFormStatus() { + if (!valid() || !has_requested_consent_info_update_) { + return kConsentFormStatusUnknown; + } + JNIEnv* env = GetJNIEnv(); + jboolean is_available = env->CallBooleanMethod( + helper_, consent_info_helper::GetMethodId( + consent_info_helper::kIsConsentFormAvailable)); + if (env->ExceptionCheck()) { + util::CheckAndClearJniExceptions(env); + return kConsentFormStatusUnknown; + } + return (is_available == JNI_FALSE) ? kConsentFormStatusUnavailable + : kConsentFormStatusAvailable; +} + +Future ConsentInfoInternalAndroid::LoadConsentForm() { + if (LoadConsentFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); + JNIEnv* env = GetJNIEnv(); + jlong future_handle = static_cast(handle.get().id()); + + env->CallVoidMethod( + helper_, + consent_info_helper::GetMethodId(consent_info_helper::kLoadConsentForm), + future_handle); + if (env->ExceptionCheck()) { + std::string exception_message = util::GetAndClearExceptionMessage(env); + CompleteFuture(handle, kConsentFormErrorInternal, + exception_message.c_str()); + } + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalAndroid::ShowConsentForm(FormParent parent) { + if (ShowConsentFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); + JNIEnv* env = GetJNIEnv(); + + jlong future_handle = static_cast(handle.get().id()); + jboolean success = env->CallBooleanMethod( + helper_, + consent_info_helper::GetMethodId(consent_info_helper::kShowConsentForm), + future_handle, parent); + if (env->ExceptionCheck()) { + std::string exception_message = util::GetAndClearExceptionMessage(env); + CompleteFuture(handle, kConsentFormErrorInternal, + exception_message.c_str()); + } else if (success == JNI_FALSE) { + CompleteFuture( + handle, kConsentFormErrorUnavailable, + "The consent form is unavailable. Please call LoadConsentForm and " + "ensure it completes successfully before calling ShowConsentForm."); + } + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalAndroid::LoadAndShowConsentFormIfRequired( + FormParent parent) { + if (LoadAndShowConsentFormIfRequiredLastResult().status() == + kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); + + JNIEnv* env = GetJNIEnv(); + jlong future_handle = static_cast(handle.get().id()); + + env->CallVoidMethod( + helper_, + consent_info_helper::GetMethodId( + consent_info_helper::kLoadAndShowConsentFormIfRequired), + future_handle, parent); + if (env->ExceptionCheck()) { + std::string exception_message = util::GetAndClearExceptionMessage(env); + CompleteFuture(handle, kConsentFormErrorInternal, + exception_message.c_str()); + } + + return MakeFuture(futures(), handle); +} + +PrivacyOptionsRequirementStatus +ConsentInfoInternalAndroid::GetPrivacyOptionsRequirementStatus() { + if (!valid()) { + return kPrivacyOptionsRequirementStatusUnknown; + } + JNIEnv* env = GetJNIEnv(); + jint result = env->CallIntMethod( + helper_, consent_info_helper::GetMethodId( + consent_info_helper::kGetPrivacyOptionsRequirementStatus)); + return CppPrivacyOptionsRequirementStatusFromAndroid(result); +} + +Future ConsentInfoInternalAndroid::ShowPrivacyOptionsForm( + FormParent parent) { + if (ShowPrivacyOptionsFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); + + JNIEnv* env = GetJNIEnv(); + jlong future_handle = static_cast(handle.get().id()); + + env->CallVoidMethod(helper_, + consent_info_helper::GetMethodId( + consent_info_helper::kShowPrivacyOptionsForm), + future_handle, parent); + if (env->ExceptionCheck()) { + std::string exception_message = util::GetAndClearExceptionMessage(env); + CompleteFuture(handle, kConsentFormErrorInternal, + exception_message.c_str()); + } + + return MakeFuture(futures(), handle); +} + +bool ConsentInfoInternalAndroid::CanRequestAds() { + JNIEnv* env = GetJNIEnv(); + jboolean can_request = env->CallBooleanMethod( + helper_, + consent_info_helper::GetMethodId(consent_info_helper::kCanRequestAds)); + if (env->ExceptionCheck()) { + util::CheckAndClearJniExceptions(env); + return false; + } + return (can_request == JNI_FALSE) ? false : true; +} + +void ConsentInfoInternalAndroid::Reset() { + JNIEnv* env = GetJNIEnv(); + env->CallVoidMethod( + helper_, consent_info_helper::GetMethodId(consent_info_helper::kReset)); + util::CheckAndClearJniExceptions(env); +} + +JNIEnv* ConsentInfoInternalAndroid::GetJNIEnv() { + return firebase::util::GetThreadsafeJNIEnv(java_vm_); +} +jobject ConsentInfoInternalAndroid::activity() { return activity_; } + +void ConsentInfoInternalAndroid::CompleteFutureFromJniCallback( + JNIEnv* env, jint future_fn, FutureHandleId handle_id, int java_error_code, + const char* error_message) { + if (!futures()->ValidFuture(handle_id)) { + // This future is no longer valid, so no need to complete it. + return; + } + if (future_fn < 0 || future_fn >= enums().function_count) { + // Called with an invalid function ID, ignore this callback. + return; + } + FutureHandle raw_handle(handle_id); + SafeFutureHandle handle(raw_handle); + if (future_fn == enums().function_request_consent_info_update) { + // RequestConsentInfoUpdate uses the ConsentRequestError enum. + ConsentRequestError error_code = CppConsentRequestErrorFromAndroidFormError( + java_error_code, error_message); + CompleteFuture(handle, error_code, error_message); + } else { + // All other methods use the ConsentFormError enum. + ConsentFormError error_code = + CppConsentFormErrorFromAndroidFormError(java_error_code, error_message); + CompleteFuture(handle, error_code, error_message); + } +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/ump/src/android/ump/consent_info_internal_android.h b/ump/src/android/ump/consent_info_internal_android.h new file mode 100644 index 0000000000..4c0498671f --- /dev/null +++ b/ump/src/android/ump/consent_info_internal_android.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ +#define FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ + +#include + +#include "app/src/util_android.h" +#include "firebase/internal/mutex.h" +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +class ConsentInfoInternalAndroid : public ConsentInfoInternal { + public: + ConsentInfoInternalAndroid(JNIEnv* env, jobject activity); + ~ConsentInfoInternalAndroid() override; + + ConsentStatus GetConsentStatus() override; + ConsentFormStatus GetConsentFormStatus() override; + + Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) override; + Future LoadConsentForm() override; + Future ShowConsentForm(FormParent parent) override; + + Future LoadAndShowConsentFormIfRequired(FormParent parent) override; + + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus() override; + Future ShowPrivacyOptionsForm(FormParent parent) override; + + bool CanRequestAds() override; + + void Reset() override; + + bool valid() { return (helper_ != nullptr); } + + JNIEnv* GetJNIEnv(); + jobject activity(); + + private: + struct EnumCache { + jint consentstatus_unknown; + jint consentstatus_required; + jint consentstatus_not_required; + jint consentstatus_obtained; + + jint formerror_success; + jint formerror_internal; + jint formerror_network; + jint formerror_invalid_operation; + jint formerror_timeout; + + jint debug_geography_disabled; + jint debug_geography_eea; + jint debug_geography_not_eea; + + jint privacy_options_requirement_unknown; + jint privacy_options_requirement_required; + jint privacy_options_requirement_not_required; + + jint function_request_consent_info_update; + jint function_load_consent_form; + jint function_show_consent_form; + jint function_load_and_show_consent_form_if_required; + jint function_show_privacy_options_form; + jint function_count; + }; + + // JNI native method callback for ConsentInfoHelper.completeFuture. + // Calls CompleteFutureFromJniCallback() below. + static void JNI_ConsentInfoHelper_completeFuture( + JNIEnv* env, jclass clazz, jint future_fn, + jlong consent_info_internal_ptr, jlong future_handle, jint error_code, + jobject error_message_obj); + + // Complete the given Future when called from JNI. + void CompleteFutureFromJniCallback(JNIEnv* env, jint future_fn, + FutureHandleId handle_id, int error_code, + const char* error_message); + + // Cache Java enum field values in the struct below. + void CacheEnumValues(JNIEnv* env); + + // Enum conversion methods. + ConsentStatus CppConsentStatusFromAndroid(jint status); + PrivacyOptionsRequirementStatus CppPrivacyOptionsRequirementStatusFromAndroid( + jint status); + jint AndroidDebugGeographyFromCppDebugGeography(ConsentDebugGeography geo); + ConsentRequestError CppConsentRequestErrorFromAndroidFormError( + jint error, const char* message = nullptr); + ConsentFormError CppConsentFormErrorFromAndroidFormError( + jint error, const char* message = nullptr); + + const EnumCache& enums() { return enums_; } + + static ConsentInfoInternalAndroid* s_instance; + static firebase::Mutex s_instance_mutex; + + EnumCache enums_; + + JavaVM* java_vm_; + jobject activity_; + jobject helper_; + + // Needed for GetConsentFormStatus to return Unknown. + bool has_requested_consent_info_update_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ diff --git a/ump/src/common/ump/consent_info.cc b/ump/src/common/ump/consent_info.cc new file mode 100644 index 0000000000..334a11d735 --- /dev/null +++ b/ump/src/common/ump/consent_info.cc @@ -0,0 +1,184 @@ +/* + * 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. + */ + +#include "firebase/gma/ump/consent_info.h" + +#include "app/src/assert.h" +#include "firebase/app.h" +#include "firebase/gma/ump.h" +#include "firebase/internal/platform.h" +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { + +ConsentInfo* ConsentInfo::s_instance_ = nullptr; + +ConsentInfo* ConsentInfo::GetInstance(const ::firebase::App& app, + ::firebase::InitResult* init_result_out) { + if (s_instance_) { + if (init_result_out) *init_result_out = kInitResultSuccess; + return s_instance_; + } +#if FIREBASE_PLATFORM_ANDROID + return GetInstance(app.GetJNIEnv(), app.activity(), init_result_out); +#else // !FIREBASE_PLATFORM_ANDROID + return GetInstance(init_result_out); +#endif // FIREBASE_PLATFORM_ANDROID +} + +#if FIREBASE_PLATFORM_ANDROID +ConsentInfo* ConsentInfo::GetInstance() { return s_instance_; } + +ConsentInfo* ConsentInfo::GetInstance(JNIEnv* jni_env, jobject activity, + ::firebase::InitResult* init_result_out) { +#else // !FIREBASE_PLATFORM_ANDROID +ConsentInfo* ConsentInfo::GetInstance(::firebase::InitResult* init_result_out) { +#endif + if (s_instance_) { + if (init_result_out) *init_result_out = kInitResultSuccess; + return s_instance_; + } + + ConsentInfo* consent_info = new ConsentInfo(); +#if FIREBASE_PLATFORM_ANDROID + InitResult result = consent_info->Initialize(jni_env, activity); +#else + InitResult result = consent_info->Initialize(); +#endif + if (init_result_out) *init_result_out = result; + if (result != kInitResultSuccess) { + delete consent_info; + return nullptr; + } + return consent_info; +} + +ConsentInfo::ConsentInfo() { + internal_ = nullptr; +#if FIREBASE_PLATFORM_ANDROID + java_vm_ = nullptr; +#endif + s_instance_ = this; +} + +ConsentInfo::~ConsentInfo() { + if (internal_) { + delete internal_; + internal_ = nullptr; + } + s_instance_ = nullptr; +} + +#if FIREBASE_PLATFORM_ANDROID +InitResult ConsentInfo::Initialize(JNIEnv* jni_env, jobject activity) { + FIREBASE_ASSERT(!internal_); + internal_ = internal::ConsentInfoInternal::CreateInstance(jni_env, activity); + return internal_ ? kInitResultSuccess : kInitResultFailedMissingDependency; +} +#else +InitResult ConsentInfo::Initialize() { + FIREBASE_ASSERT(!internal_); + internal_ = internal::ConsentInfoInternal::CreateInstance(); + return kInitResultSuccess; +} +#endif + +// Below this, everything is a passthrough to ConsentInfoInternal. If there is +// no internal_ pointer (e.g. it's been cleaned up), return default values and +// invalid futures. + +ConsentStatus ConsentInfo::GetConsentStatus() { + if (!internal_) return kConsentStatusUnknown; + return internal_->GetConsentStatus(); +} + +ConsentFormStatus ConsentInfo::GetConsentFormStatus() { + if (!internal_) return kConsentFormStatusUnknown; + return internal_->GetConsentFormStatus(); +} + +Future ConsentInfo::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + if (!internal_) return Future(); + return internal_->RequestConsentInfoUpdate(params); +} + +Future ConsentInfo::RequestConsentInfoUpdateLastResult() { + if (!internal_) return Future(); + return internal_->RequestConsentInfoUpdateLastResult(); +} + +Future ConsentInfo::LoadConsentForm() { + if (!internal_) return Future(); + return internal_->LoadConsentForm(); +} + +Future ConsentInfo::LoadConsentFormLastResult() { + if (!internal_) return Future(); + return internal_->LoadConsentFormLastResult(); +} + +Future ConsentInfo::ShowConsentForm(FormParent parent) { + if (!internal_) return Future(); + return internal_->ShowConsentForm(parent); +} + +Future ConsentInfo::ShowConsentFormLastResult() { + if (!internal_) return Future(); + return internal_->ShowConsentFormLastResult(); +} + +Future ConsentInfo::LoadAndShowConsentFormIfRequired(FormParent parent) { + if (!internal_) return Future(); + return internal_->LoadAndShowConsentFormIfRequired(parent); +} + +Future ConsentInfo::LoadAndShowConsentFormIfRequiredLastResult() { + if (!internal_) return Future(); + return internal_->LoadAndShowConsentFormIfRequiredLastResult(); +} + +PrivacyOptionsRequirementStatus +ConsentInfo::GetPrivacyOptionsRequirementStatus() { + if (!internal_) return kPrivacyOptionsRequirementStatusUnknown; + return internal_->GetPrivacyOptionsRequirementStatus(); +} + +Future ConsentInfo::ShowPrivacyOptionsForm(FormParent parent) { + if (!internal_) return Future(); + return internal_->ShowPrivacyOptionsForm(parent); +} + +Future ConsentInfo::ShowPrivacyOptionsFormLastResult() { + if (!internal_) return Future(); + return internal_->ShowPrivacyOptionsFormLastResult(); +} + +bool ConsentInfo::CanRequestAds() { + if (!internal_) return false; + return internal_->CanRequestAds(); +} + +void ConsentInfo::Reset() { + if (!internal_) return; + internal_->Reset(); +} + +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/ump/src/common/ump/consent_info_internal.cc b/ump/src/common/ump/consent_info_internal.cc new file mode 100644 index 0000000000..3a3e9cd4b8 --- /dev/null +++ b/ump/src/common/ump/consent_info_internal.cc @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#include "gma/src/common/ump/consent_info_internal.h" + +#include "app/src/include/firebase/internal/platform.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +ConsentInfoInternal::ConsentInfoInternal() : futures_(kConsentInfoFnCount) {} + +ConsentInfoInternal::~ConsentInfoInternal() {} + +const char* ConsentInfoInternal::GetConsentRequestErrorMessage( + ConsentRequestError error_code) { + switch (error_code) { + case kConsentRequestSuccess: + return "Success"; + case kConsentRequestErrorInvalidAppId: +#if FIREBASE_PLATFORM_ANDROID + return "Missing or invalid com.google.android.gms.ads.APPLICATION_ID in " + "AndroidManifest.xml"; +#elif FIREBASE_PLATFORM_IOS + return "Missing or invalid GADApplicationidentifier in Info.plist"; +#else + return "Missing or invalid App ID"; +#endif + case kConsentRequestErrorNetwork: + return "Network error"; + case kConsentRequestErrorInternal: + return "Internal error"; + case kConsentRequestErrorMisconfiguration: + return "A misconfiguration exists in the UI"; + case kConsentRequestErrorUnknown: + return "Unknown error"; + case kConsentRequestErrorInvalidOperation: + return "Invalid operation"; + case kConsentRequestErrorOperationInProgress: + return "Operation already in progress. Please wait for it to finish by " + "checking RequestConsentInfoUpdateLastResult()."; + default: + return "Bad error code"; + } +} + +const char* ConsentInfoInternal::GetConsentFormErrorMessage( + ConsentFormError error_code) { + switch (error_code) { + case kConsentFormSuccess: + return "Success"; + case kConsentFormErrorTimeout: + return "Timed out"; + case kConsentFormErrorUnavailable: + return "The form is unavailable."; + case kConsentFormErrorInternal: + return "Internal error"; + case kConsentFormErrorUnknown: + return "Unknown error"; + case kConsentFormErrorAlreadyUsed: + return "The form was already used"; + case kConsentFormErrorInvalidOperation: + return "Invalid operation"; + case kConsentFormErrorOperationInProgress: + return "Operation already in progress. Please wait for it to finish by " + "checking LoadFormLastResult() or ShowFormLastResult()."; + default: + return "Bad error code"; + } +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/ump/src/common/ump/consent_info_internal.h b/ump/src/common/ump/consent_info_internal.h new file mode 100644 index 0000000000..94d87a5ec0 --- /dev/null +++ b/ump/src/common/ump/consent_info_internal.h @@ -0,0 +1,142 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ +#define FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ + +#include "app/src/cleanup_notifier.h" +#include "app/src/reference_counted_future_impl.h" +#include "firebase/future.h" +#include "firebase/gma/ump.h" +#include "firebase/gma/ump/types.h" +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#endif + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// Constants representing each ConsentInfo function that returns a Future. +enum ConsentInfoFn { + kConsentInfoFnRequestConsentInfoUpdate, + kConsentInfoFnLoadConsentForm, + kConsentInfoFnShowConsentForm, + kConsentInfoFnLoadAndShowConsentFormIfRequired, + kConsentInfoFnShowPrivacyOptionsForm, + kConsentInfoFnCount +}; + +class ConsentInfoInternal { + public: + virtual ~ConsentInfoInternal(); + + // Implemented in platform-specific code to instantiate a + // platform-specific subclass. +#if FIREBASE_PLATFORM_ANDROID + static ConsentInfoInternal* CreateInstance(JNIEnv* jni_env, jobject activity); +#else + static ConsentInfoInternal* CreateInstance(); +#endif + + virtual ConsentStatus GetConsentStatus() = 0; + virtual ConsentFormStatus GetConsentFormStatus() = 0; + + virtual Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) = 0; + Future RequestConsentInfoUpdateLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnRequestConsentInfoUpdate)); + } + virtual Future LoadConsentForm() = 0; + + Future LoadConsentFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnLoadConsentForm)); + } + + virtual Future ShowConsentForm(FormParent parent) = 0; + + Future ShowConsentFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnShowConsentForm)); + } + + virtual Future LoadAndShowConsentFormIfRequired(FormParent parent) = 0; + + Future LoadAndShowConsentFormIfRequiredLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnLoadAndShowConsentFormIfRequired)); + } + + virtual PrivacyOptionsRequirementStatus + GetPrivacyOptionsRequirementStatus() = 0; + + virtual Future ShowPrivacyOptionsForm(FormParent parent) = 0; + + Future ShowPrivacyOptionsFormLastResult() { + return static_cast&>( + futures()->LastResult(kConsentInfoFnShowPrivacyOptionsForm)); + } + + virtual bool CanRequestAds() = 0; + + virtual void Reset() = 0; + + protected: + ConsentInfoInternal(); + + static const char* GetConsentRequestErrorMessage( + ConsentRequestError error_code); + + static const char* GetConsentFormErrorMessage(ConsentFormError error_code); + + SafeFutureHandle CreateFuture() { return futures()->SafeAlloc(); } + SafeFutureHandle CreateFuture(ConsentInfoFn fn_idx) { + return futures()->SafeAlloc(fn_idx); + } + + // Complete a Future with the given error code. + void CompleteFuture(SafeFutureHandle handle, ConsentRequestError error, + const char* message = nullptr) { + return futures()->Complete( + handle, error, + message ? message : GetConsentRequestErrorMessage(error)); + } + // Complete the future with the given error code. + void CompleteFuture(SafeFutureHandle handle, ConsentFormError error, + const char* message = nullptr) { + return futures()->Complete( + handle, error, message ? message : GetConsentFormErrorMessage(error)); + } + + ReferenceCountedFutureImpl* futures() { return &futures_; } + CleanupNotifier* cleanup() { return &cleanup_; } + + private: + ReferenceCountedFutureImpl futures_; + CleanupNotifier cleanup_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ diff --git a/ump/src/include/firebase/gma.h b/ump/src/include/firebase/gma.h new file mode 100644 index 0000000000..0d95f536f5 --- /dev/null +++ b/ump/src/include/firebase/gma.h @@ -0,0 +1,243 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ + +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#endif // FIREBASE_PLATFORM_ANDROID + +#include + +#include "firebase/app.h" +#include "firebase/gma/ad_view.h" +#include "firebase/gma/internal/native_ad.h" +#include "firebase/gma/internal/query_info.h" +#include "firebase/gma/interstitial_ad.h" +#include "firebase/gma/rewarded_ad.h" +#include "firebase/gma/types.h" +#include "firebase/internal/common.h" + +#if !defined(DOXYGEN) && !defined(SWIG) +FIREBASE_APP_REGISTER_CALLBACKS_REFERENCE(gma) +#endif // !defined(DOXYGEN) && !defined(SWIG) + +namespace firebase { +// In the GMA docs, link to firebase::Future in the Firebase C++ docs. +#if defined(DOXYGEN_ADMOB) +/// @brief The Google Mobile Ads C++ SDK uses this class to return results from +/// asynchronous operations. All C++ functions and method calls that operate +/// asynchronously return a %Future, and provide a "LastResult" +/// function to retrieve the most recent %Future result. +/// +/// The Google Mobile Ads C++ SDK uses this class from the Firebase C++ SDK to +/// return results from asynchronous operations. For more information, see the +/// Firebase +/// C++ SDK documentation. +template +class Future { + // Empty class (used for documentation only). +}; + +/// @brief Firebase App class. For more information, see the Firebase +/// C++ SDK documentation. +class App { + // Empty class (used for documentation only). +}; + +#endif // defined(DOXYGEN_ADMOB) + +/// @brief API for Google Mobile Ads with Firebase. +/// +/// The GMA API allows you to load and display mobile ads using the Google +/// Mobile Ads SDK. Each ad format has its own header file. +/// +/// @deprecated **The Google Mobile Ads (GMA) C++ SDK is _deprecated_ as of June +/// 17, 2024 and should not be adopted in projects that don't already use it. It +/// will enter _End-of-Maintenance (EoM)_ on June 17, 2025. Note that versions +/// of the SDK released before the EoM date will continue to function, but no +/// further bug fixes or changes will be released after the EoM date.** +/// +/// Instead of the Google Mobile Ads C++ SDK, consider using the +/// [iOS](/docs/admob/ios/quick-start) and +/// [Android](/docs/admob/android/quick-start) SDKs from AdMob. For support, +/// reach out to the [Google Mobile Ads SDK Technical +/// Forum](https://groups.google.com/g/google-admob-ads-sdk). +namespace gma { + +/// Initializes Google Mobile Ads (GMA) via Firebase. +/// +/// @param[in] app The Firebase app for which to initialize mobile ads. +/// +/// @param[out] init_result_out Optional: If provided, write the basic init +/// result here. kInitResultSuccess if initialization succeeded, or +/// kInitResultFailedMissingDependency on Android if Google Play services is not +/// available on the current device and the Google Mobile Ads SDK requires +/// Google Play services (for example, when using 'play-services-ads-lite'). +/// Note that this does not include the adapter initialization status, which is +/// returned in the Future. +/// +/// @return If init_result_out is kInitResultSuccess, this Future will contain +/// the initialization status of each adapter once initialization is complete. +/// Otherwise, the returned Future will have kFutureStatusInvalid. +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// the [SDK reference +/// documentation]( +/// /admob/cpp/reference/namespace/firebase/gma) +/// for more information. +FIREBASE_DEPRECATED Future Initialize( + const ::firebase::App& app, InitResult* init_result_out = nullptr); + +#if FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) +/// Initializes Google Mobile Ads (GMA) without Firebase for Android. +/// +/// The arguments to @ref Initialize are platform-specific so the caller must do +/// something like this: +/// @code +/// #if defined(__ANDROID__) +/// firebase::gma::Initialize(jni_env, activity); +/// #else +/// firebase::gma::Initialize(); +/// #endif +/// @endcode +/// +/// @param[in] jni_env JNIEnv pointer. +/// @param[in] activity Activity used to start the application. +/// @param[out] init_result_out Optional: If provided, write the basic init +/// result here. kInitResultSuccess if initialization succeeded, or +/// kInitResultFailedMissingDependency on Android if Google Play services is not +/// available on the current device and the Google Mobile Ads SDK requires +/// Google Play services (for example, when using 'play-services-ads-lite'). +/// Note that this does not include the adapter initialization status, which is +/// returned in the Future. +/// +/// @return If init_result_out is kInitResultSuccess, this Future will contain +/// the initialization status of each adapter once initialization is complete. +/// Otherwise, the returned Future will have kFutureStatusInvalid. +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma +/// for more information. +FIREBASE_DEPRECATED Future Initialize( + JNIEnv* jni_env, jobject activity, InitResult* init_result_out = nullptr); + +#endif // defined(__ANDROID__) || defined(DOXYGEN) +#if !FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) +/// Initializes Google Mobile Ads (GMA) without Firebase for iOS. +/// +/// @param[out] init_result_out Optional: If provided, write the basic init +/// result here. kInitResultSuccess if initialization succeeded, or +/// kInitResultFailedMissingDependency on Android if Google Play services is not +/// available on the current device and the Google Mobile Ads SDK requires +/// Google Play services (for example, when using 'play-services-ads-lite'). +/// Note that this does not include the adapter initialization status, which is +/// returned in the Future. +/// +/// @return If init_result_out is kInitResultSuccess, this Future +/// will contain the initialization status of each adapter once initialization +/// is complete. Otherwise, the returned Future will have +/// kFutureStatusInvalid. +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma +/// for more information. +FIREBASE_DEPRECATED Future Initialize( + InitResult* init_result_out = nullptr); +#endif // !defined(__ANDROID__) || defined(DOXYGEN) + +/// Get the Future returned by a previous call to +/// @ref firebase::gma::Initialize(). +Future InitializeLastResult(); + +/// Get the current adapter initialization status. You can poll this method to +/// check which adapters have been initialized. +AdapterInitializationStatus GetInitializationStatus(); + +/// Disables automated SDK crash reporting on iOS. If not called, the SDK +/// records the original exception handler if available and registers a new +/// exception handler. The new exception handler only reports SDK related +/// exceptions and calls the recorded original exception handler. +/// +/// This method has no effect on Android. +void DisableSDKCrashReporting(); + +/// Disables mediation adapter initialization on iOS during initialization of +/// the GMA SDK. Calling this method may negatively impact your ad +/// performance and should only be called if you will not use GMA SDK +/// controlled mediation during this app session. This method must be called +/// before initializing the GMA SDK or loading ads and has no effect once the +/// SDK has been initialized. +/// +/// This method has no effect on Android. +void DisableMediationInitialization(); + +/// Sets the global @ref RequestConfiguration that will be used for +/// every @ref AdRequest during the app's session. +/// +/// @param[in] request_configuration The request configuration that should be +/// applied to all ad requests. +void SetRequestConfiguration(const RequestConfiguration& request_configuration); + +/// Gets the global RequestConfiguration. +/// +/// @return the currently active @ref RequestConfiguration that's being +/// used for every ad request. +/// @note: on iOS, the +/// @ref RequestConfiguration::tag_for_child_directed_treatment and +/// @ref RequestConfiguration::tag_for_under_age_of_consent fields will be set +/// to RequestConfiguration.kChildDirectedTreatmentUnspecified, and +/// RequestConfiguration.kUnderAgeOfConsentUnspecified, respectfully. +RequestConfiguration GetRequestConfiguration(); + +/// Opens the ad inspector UI. +/// +/// @param[in] parent The platform-specific UI element that will host the +/// ad inspector. For iOS this should be the window's +/// UIViewController. For Android this is the +/// Activity Context which the GMA SDK is running in. +/// @param[in] listener The listener will be invoked when the user closes +/// the ad inspector UI. @ref firebase::gma::Initialize(). must be called +/// prior to this function. +void OpenAdInspector(AdParent parent, AdInspectorClosedListener* listener); + +/// Controls whether the Google Mobile Ads SDK Same App Key is enabled. +/// +/// This function must be invoked after GMA has been initialized. The value set +/// persists across app sessions. The key is enabled by default. +/// +/// This operation is supported on iOS only. This is a no-op on Android +/// systems. +/// +/// @param[in] is_enabled whether the Google Mobile Ads SDK Same App Key is +/// enabled. +void SetIsSameAppKeyEnabled(bool is_enabled); + +/// @brief Terminate GMA. +/// +/// Frees resources associated with GMA that were allocated during +/// @ref firebase::gma::Initialize(). +void Terminate(); + +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ diff --git a/ump/src/include/firebase/gma/ad_view.h b/ump/src/include/firebase/gma/ad_view.h new file mode 100644 index 0000000000..d2d73db67d --- /dev/null +++ b/ump/src/include/firebase/gma/ad_view.h @@ -0,0 +1,272 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ + +#include "firebase/future.h" +#include "firebase/gma/types.h" +#include "firebase/internal/common.h" + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class AdViewInternal; +} // namespace internal + +class AdViewBoundingBoxListener; +struct BoundingBox; + +/// @brief Loads and displays Google Mobile Ads AdView ads. +/// +/// Each AdView object corresponds to a single GMA ad placement of a specified +/// size. There are methods to load an ad, move it, show it and hide it, and +/// retrieve the bounds of the ad onscreen. +/// +/// AdView objects provide information about their current state through +/// Futures. Methods like @ref Initialize, @ref LoadAd, and @ref Hide each have +/// a corresponding @ref Future from which the result of the last call can be +/// determined. The two variants of @ref SetPosition share a single result +/// @ref Future, since they're essentially the same action. +/// +/// For example, you could initialize, load, and show an AdView while +/// checking the result of the previous action at each step as follows: +/// +/// @code +/// namespace gma = ::firebase::gma; +/// gma::AdView* ad_view = new gma::AdView(); +/// ad_view->Initialize(ad_parent, "YOUR_AD_UNIT_ID", desired_ad_size) +/// @endcode +/// +/// Then, later: +/// +/// @code +/// if (ad_view->InitializeLastResult().status() == +/// ::firebase::kFutureStatusComplete && +/// ad_view->InitializeLastResult().error() == +/// firebase::gma::kAdErrorCodeNone) { +/// ad_view->LoadAd(your_ad_request); +/// } +/// @endcode +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma +/// for more information. +class AdView { + public: + /// The possible screen positions for a @ref AdView, configured via + /// @ref SetPosition. + enum Position { + /// The position isn't one of the predefined screen locations. + kPositionUndefined = -1, + /// Top of the screen, horizontally centered. + kPositionTop = 0, + /// Bottom of the screen, horizontally centered. + kPositionBottom, + /// Top-left corner of the screen. + kPositionTopLeft, + /// Top-right corner of the screen. + kPositionTopRight, + /// Bottom-left corner of the screen. + kPositionBottomLeft, + /// Bottom-right corner of the screen. + kPositionBottomRight, + }; + + /// Creates an uninitialized @ref AdView object. + /// @ref Initialize must be called before the object is used. + FIREBASE_DEPRECATED AdView(); + + ~AdView(); + + /// Initializes the @ref AdView object. + /// @param[in] parent The platform-specific UI element that will host the ad. + /// @param[in] ad_unit_id The ad unit ID to use when requesting ads. + /// @param[in] size The desired ad size for the ad. + FIREBASE_DEPRECATED Future Initialize(AdParent parent, + const char* ad_unit_id, + const AdSize& size); + + /// Returns a @ref Future that has the status of the last call to + /// @ref Initialize. + FIREBASE_DEPRECATED Future InitializeLastResult() const; + + /// Begins an asynchronous request for an ad. If successful, the ad will + /// automatically be displayed in the AdView. + /// @param[in] request An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future LoadAd(const AdRequest& request); + + /// Returns a @ref Future containing the status of the last call to + /// @ref LoadAd. + Future LoadAdLastResult() const; + + /// Retrieves the @ref AdView's current onscreen size and location. + /// + /// @return The current size and location. Values are in pixels, and location + /// coordinates originate from the top-left corner of the screen. + BoundingBox bounding_box() const; + + /// Sets an AdListener for this ad view. + /// + /// @param[in] listener An AdListener object which will be invoked + /// when lifecycle events occur on this AdView. + void SetAdListener(AdListener* listener); + + /// Sets a listener to be invoked when the Ad's bounding box + /// changes size or location. + /// + /// @param[in] listener A AdViewBoundingBoxListener object which will be + /// invoked when the ad changes size, shape, or position. + void SetBoundingBoxListener(AdViewBoundingBoxListener* listener); + + /// Sets a listener to be invoked when this ad is estimated to have earned + /// money. + /// + /// @param[in] listener A PaidEventListener object to be invoked when a + /// paid event occurs on the ad. + void SetPaidEventListener(PaidEventListener* listener); + + /// Moves the @ref AdView so that its top-left corner is located at + /// (x, y). Coordinates are in pixels from the top-left corner of the screen. + /// + /// When built for Android, the library will not display an ad on top of or + /// beneath an Activity's status bar. If a call to SetPosition + /// would result in an overlap, the @ref AdView is placed just below the + /// status bar, so no overlap occurs. + /// @param[in] x The desired horizontal coordinate. + /// @param[in] y The desired vertical coordinate. + /// + /// @return a @ref Future which will be completed when this move operation + /// completes. + Future SetPosition(int x, int y); + + /// Moves the @ref AdView so that it's located at the given predefined + /// position. + /// + /// @param[in] position The predefined position to which to move the + /// @ref AdView. + /// + /// @return a @ref Future which will be completed when this move operation + /// completes. + Future SetPosition(Position position); + + /// Returns a @ref Future containing the status of the last call to either + /// version of @ref SetPosition. + Future SetPositionLastResult() const; + + /// Hides the AdView. + Future Hide(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Hide. + Future HideLastResult() const; + + /// Shows the @ref AdView. + Future Show(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Show. + Future ShowLastResult() const; + + /// Pauses the @ref AdView. Should be called whenever the C++ engine + /// pauses or the application loses focus. + Future Pause(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Pause. + Future PauseLastResult() const; + + /// Resumes the @ref AdView after pausing. + Future Resume(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Resume. + Future ResumeLastResult() const; + + /// Cleans up and deallocates any resources used by the @ref AdView. + /// You must call this asynchronous operation before this object's destructor + /// is invoked or risk leaking device resources. + Future Destroy(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Destroy. + Future DestroyLastResult() const; + + /// Returns the AdSize of the AdView. + /// + /// @return An @ref AdSize object representing the size of the ad. If this + /// view has not been initialized then the AdSize will be 0,0. + AdSize ad_size() const; + + protected: + /// Pointer to a listener for AdListener events. + AdListener* ad_listener_; + + /// Pointer to a listener for BoundingBox events. + AdViewBoundingBoxListener* ad_view_bounding_box_listener_; + + /// Pointer to a listener for paid events. + PaidEventListener* paid_event_listener_; + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::AdViewInternal* internal_; +}; + +/// A listener class that developers can extend and pass to an @ref AdView +/// object's @ref AdView::SetBoundingBoxListener method to be notified of +/// changes to the size of the Ad's bounding box. +class AdViewBoundingBoxListener { + public: + virtual ~AdViewBoundingBoxListener(); + + /// This method is called when the @ref AdView object's bounding box + /// changes. + /// + /// @param[in] ad_view The view whose bounding box changed. + /// @param[in] box The new bounding box. + virtual void OnBoundingBoxChanged(AdView* ad_view, BoundingBox box) = 0; +}; + +/// @brief The screen location and dimensions of an AdView once it has been +/// initialized. +struct BoundingBox { + /// Default constructor which initializes all member variables to 0. + BoundingBox() + : height(0), width(0), x(0), y(0), position(AdView::kPositionUndefined) {} + + /// Height of the ad in pixels. + int height; + /// Width of the ad in pixels. + int width; + /// Horizontal position of the ad in pixels from the left. + int x; + /// Vertical position of the ad in pixels from the top. + int y; + + /// The position of the AdView if one has been set as the target position, or + /// kPositionUndefined otherwise. + AdView::Position position; +}; + +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ diff --git a/ump/src/include/firebase/gma/internal/README.md b/ump/src/include/firebase/gma/internal/README.md new file mode 100644 index 0000000000..ce08eafee1 --- /dev/null +++ b/ump/src/include/firebase/gma/internal/README.md @@ -0,0 +1,6 @@ +# Firebase C++ Open Source Development + +**NOTE: For internal use only.** + +This folder contains experimental header files that are internal and experimental. +Users shouldn't expect any stability guarantees for code in this folder, and any public usage is discouraged and is not supported. diff --git a/ump/src/include/firebase/gma/internal/native_ad.h b/ump/src/include/firebase/gma/internal/native_ad.h new file mode 100644 index 0000000000..a35d8f90b4 --- /dev/null +++ b/ump/src/include/firebase/gma/internal/native_ad.h @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ + +#include +#include + +#include "firebase/future.h" +#include "firebase/gma/types.h" +#include "firebase/internal/common.h" +#include "firebase/variant.h" + +// Doxygen breaks trying to parse this file, and since it is internal logic, +// it doesn't need to be included in the generated documentation. +#ifndef DOXYGEN + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class NativeAdInternal; +} // namespace internal + +struct NativeAdImageInternal; + +class GmaInternal; +class NativeAdImage; +class ImageResult; + +class NativeAd { + public: + NativeAd(); + ~NativeAd(); + + /// Initialize the NativeAd object. + /// parent: The platform-specific UI element that will host the ad. + Future Initialize(AdParent parent); + + /// Returns a Future containing the status of the last call to + /// Initialize. + Future InitializeLastResult() const; + + /// Begins an asynchronous request for an ad. + /// + /// ad_unit_id: The ad unit ID to use in loading the ad. + /// request: An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future LoadAd(const char* ad_unit_id, const AdRequest& request); + + /// Returns a Future containing the status of the last call to + /// LoadAd. + Future LoadAdLastResult() const; + + /// Sets an AdListener for this native ad. + /// + /// @param[in] listener An AdListener object which will be invoked + /// when lifecycle events occur on this NativeAd. + void SetAdListener(AdListener* listener); + + /// Returns the associated icon asset of the native ad. + const NativeAdImage& icon() const; + + /// Returns the associated image assets of the native ad. + const std::vector& images() const; + + // Returns the associated adchoices icon asset of the native ad. + const NativeAdImage& adchoices_icon() const; + + /// Only allowlisted ad units use this api. + Future RecordImpression(const Variant& impression_data); + + /// Returns a Future containing the status of the last call to + /// RecordImpression. + Future RecordImpressionLastResult() const; + + /// Only allowlisted ad units use this api. + Future PerformClick(const Variant& click_data); + + /// Returns a Future containing the status of the last call to + /// PerformClick. + Future PerformClickLastResult() const; + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::NativeAdInternal* internal_; +}; + +/// Information about the result of a load image operation. +class ImageResult { + public: + /// Default Constructor. + ImageResult(); + + /// Destructor. + virtual ~ImageResult(); + + /// Returns true if the operation was successful. + bool is_successful() const; + + /// If the ImageResult::is_successful() returned false, then the + /// vector returned via this method will contain no contextual + /// information. + const std::vector& image() const; + + private: + friend class GmaInternal; + + /// Constructor invoked upon successful image load. + explicit ImageResult(const std::vector& image_info); + + /// Denotes if the ImageResult represents a success or an error. + bool is_successful_; + + /// Contains the loaded image asset. + std::vector image_info_; +}; + +class NativeAdImage { + public: + /// Default Constructor. + NativeAdImage(); + + /// Copy Constructor. + NativeAdImage(const NativeAdImage& source_native_image); + + /// Returns the image scale, which denotes the ratio of pixels to dp. + double scale() const; + + /// Returns the image uri. + const std::string& image_uri() const; + + /// Begins an asynchronous request for loading an image asset. + Future LoadImage() const; + + // Returns a Future containing the status of the last call to + // LoadImage. + Future LoadImageLastResult() const; + + virtual ~NativeAdImage(); + + /// Assignment operator. + NativeAdImage& operator=(const NativeAdImage& obj); + + private: + friend class NativeAd; + friend class GmaInternal; + + explicit NativeAdImage(const NativeAdImageInternal& native_ad_image_internal); + + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + NativeAdImageInternal* internal_; +}; + +} // namespace gma +} // namespace firebase + +#endif // DOXYGEN + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ diff --git a/ump/src/include/firebase/gma/internal/query_info.h b/ump/src/include/firebase/gma/internal/query_info.h new file mode 100644 index 0000000000..b2d77959dc --- /dev/null +++ b/ump/src/include/firebase/gma/internal/query_info.h @@ -0,0 +1,120 @@ +/* + * Copyright 2024 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ + +#include + +#include "firebase/future.h" +#include "firebase/gma/types.h" + +// Doxygen breaks trying to parse this file, and since it is internal logic, +// it doesn't need to be included in the generated documentation. +#ifndef DOXYGEN + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class QueryInfoInternal; +} // namespace internal + +class GmaInternal; +class QueryInfoResult; + +class QueryInfo { + public: + QueryInfo(); + ~QueryInfo(); + + /// Initialize the QueryInfo object. + /// parent: The platform-specific application context. + Future Initialize(AdParent parent); + + /// Returns a Future containing the status of the last call to + /// Initialize. + Future InitializeLastResult() const; + + /// Begins an asynchronous request for creating a query info string. + /// + /// format: The format of the ad for which the query info is being created. + /// request: An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future CreateQueryInfo(AdFormat format, + const AdRequest& request); + + /// Returns a Future containing the status of the last call to + /// CreateQueryInfo. + Future CreateQueryInfoLastResult() const; + + /// Begins an asynchronous request for creating a query info string. + /// + /// format: The format of the ad for which the query info is being created. + /// request: An AdRequest struct with information about the request + /// to be made (such as targeting info). + /// ad_unit_id: The ad unit ID to use in loading the ad. + Future CreateQueryInfoWithAdUnit(AdFormat format, + const AdRequest& request, + const char* ad_unit_id); + + /// Returns a Future containing the status of the last call to + /// CreateQueryInfoWithAdUnit. + Future CreateQueryInfoWithAdUnitLastResult() const; + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::QueryInfoInternal* internal_; +}; + +/// Information about the result of a create queryInfo operation. +class QueryInfoResult { + public: + /// Default Constructor. + QueryInfoResult(); + + /// Destructor. + virtual ~QueryInfoResult(); + + /// Returns true if the operation was successful. + bool is_successful() const; + + /// If the QueryInfoResult::is_successful() returned false, then the + /// string returned via this method will contain no contextual + /// information. + const std::string& query_info() const; + + private: + friend class GmaInternal; + + /// Constructor invoked upon successful query info generation. + explicit QueryInfoResult(const std::string& query_info); + + /// Denotes if the QueryInfoResult represents a success or an error. + bool is_successful_; + + /// Contains the full query info string. + std::string query_info_; +}; + +} // namespace gma +} // namespace firebase + +#endif // DOXYGEN + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ diff --git a/ump/src/include/firebase/gma/interstitial_ad.h b/ump/src/include/firebase/gma/interstitial_ad.h new file mode 100644 index 0000000000..4173209f42 --- /dev/null +++ b/ump/src/include/firebase/gma/interstitial_ad.h @@ -0,0 +1,132 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ + +#include "firebase/future.h" +#include "firebase/gma/types.h" +#include "firebase/internal/common.h" + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class InterstitialAdInternal; +} // namespace internal + +/// @brief Loads and displays Google Mobile Ads interstitial ads. +/// +/// @ref InterstitialAd is a single-use object that can load and show a +/// single GMA interstitial ad. +/// +/// InterstitialAd objects provide information about their current state +/// through Futures. @ref Initialize, @ref LoadAd, and @ref Show each have a +/// corresponding @ref Future from which you can determine result of the +/// previous call. +/// +/// Here's how one might initialize, load, and show an interstitial ad while +/// checking against the result of the previous action at each step: +/// +/// @code +/// namespace gma = ::firebase::gma; +/// gma::InterstitialAd* interstitial = new gma::InterstitialAd(); +/// interstitial->Initialize(ad_parent); +/// @endcode +/// +/// Then, later: +/// +/// @code +/// if (interstitial->InitializeLastResult().status() == +/// ::firebase::kFutureStatusComplete && +/// interstitial->InitializeLastResult().error() == +/// firebase::gma::kAdErrorCodeNone) { +/// interstitial->LoadAd( "YOUR_AD_UNIT_ID", my_ad_request); +/// } +/// @endcode +/// +/// And after that: +/// +/// @code +/// if (interstitial->LoadAdLastResult().status() == +/// ::firebase::kFutureStatusComplete && +/// interstitial->LoadAdLastResult().error() == +/// firebase::gma::kAdErrorCodeNone)) { +/// interstitial->Show(); +/// } +/// @endcode +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma +/// for more information. +class InterstitialAd { + public: + /// Creates an uninitialized @ref InterstitialAd object. + /// @ref Initialize must be called before the object is used. + FIREBASE_DEPRECATED InterstitialAd(); + + ~InterstitialAd(); + + /// Initialize the @ref InterstitialAd object. + /// @param[in] parent The platform-specific UI element that will host the ad. + FIREBASE_DEPRECATED Future Initialize(AdParent parent); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Initialize. + FIREBASE_DEPRECATED Future InitializeLastResult() const; + + /// Begins an asynchronous request for an ad. + /// + /// @param[in] ad_unit_id The ad unit ID to use in loading the ad. + /// @param[in] request An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future LoadAd(const char* ad_unit_id, const AdRequest& request); + + /// Returns a @ref Future containing the status of the last call to + /// @ref LoadAd. + Future LoadAdLastResult() const; + + /// Shows the @ref InterstitialAd. This should not be called unless an ad has + /// already been loaded. + Future Show(); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Show. + Future ShowLastResult() const; + + /// Sets the @ref FullScreenContentListener for this @ref InterstitialAd. + /// + /// @param[in] listener A valid @ref FullScreenContentListener to receive + /// callbacks. + void SetFullScreenContentListener(FullScreenContentListener* listener); + + /// Registers a callback to be invoked when this ad is estimated to have + /// earned money + /// + /// @param[in] listener A valid @ref PaidEventListener to receive callbacks. + void SetPaidEventListener(PaidEventListener* listener); + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::InterstitialAdInternal* internal_; +}; + +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ diff --git a/ump/src/include/firebase/gma/rewarded_ad.h b/ump/src/include/firebase/gma/rewarded_ad.h new file mode 100644 index 0000000000..6aaafbb798 --- /dev/null +++ b/ump/src/include/firebase/gma/rewarded_ad.h @@ -0,0 +1,156 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ + +#include + +#include "firebase/future.h" +#include "firebase/gma/types.h" +#include "firebase/internal/common.h" + +namespace firebase { +namespace gma { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class RewardedAdInternal; +} // namespace internal + +/// @brief Loads and displays Google Mobile Ads rewarded ads. +/// +/// @ref RewardedAd is a single-use object that can load and show a +/// single GMA rewarded ad. +/// +/// RewardedAd objects provide information about their current state +/// through Futures. @ref Initialize, @ref LoadAd, and @ref Show each have a +/// corresponding @ref Future from which you can determine result of the +/// previous call. +/// +/// Here's how one might initialize, load, and show an rewarded ad while +/// checking against the result of the previous action at each step: +/// +/// @code +/// namespace gma = ::firebase::gma; +/// gma::RewardedAd* rewarded = new gma::RewardedAd(); +/// rewarded->Initialize(ad_parent); +/// @endcode +/// +/// Then, later: +/// +/// @code +/// if (rewarded->InitializeLastResult().status() == +/// ::firebase::kFutureStatusComplete && +/// rewarded->InitializeLastResult().error() == +/// firebase::gma::kAdErrorCodeNone) { +/// rewarded->LoadAd( "YOUR_AD_UNIT_ID", my_ad_request); +/// } +/// @endcode +/// +/// And after that: +/// +/// @code +/// if (rewarded->LoadAdLastResult().status() == +/// ::firebase::kFutureStatusComplete && +/// rewarded->LoadAdLastResult().error() == +/// firebase::gma::kAdErrorCodeNone)) { +/// rewarded->Show(&my_user_earned_reward_listener); +/// } +/// @endcode +/// +/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see +/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma +/// for more information. +class RewardedAd { + public: + /// Options for RewardedAd server-side verification callbacks. Set options on + /// a RewardedAd object using the @ref SetServerSideVerificationOptions + /// method. + struct ServerSideVerificationOptions { + /// Custom data to be included in server-side verification callbacks. + std::string custom_data; + + /// User id to be used in server-to-server reward callbacks. + std::string user_id; + }; + + /// Creates an uninitialized @ref RewardedAd object. + /// @ref Initialize must be called before the object is used. + FIREBASE_DEPRECATED RewardedAd(); + + ~RewardedAd(); + + /// Initialize the @ref RewardedAd object. + /// @param[in] parent The platform-specific UI element that will host the ad. + FIREBASE_DEPRECATED Future Initialize(AdParent parent); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Initialize. + FIREBASE_DEPRECATED Future InitializeLastResult() const; + + /// Begins an asynchronous request for an ad. + /// + /// @param[in] ad_unit_id The ad unit ID to use in loading the ad. + /// @param[in] request An AdRequest struct with information about the request + /// to be made (such as targeting info). + Future LoadAd(const char* ad_unit_id, const AdRequest& request); + + /// Returns a @ref Future containing the status of the last call to + /// @ref LoadAd. + Future LoadAdLastResult() const; + + /// Shows the @ref RewardedAd. This should not be called unless an ad has + /// already been loaded. + /// + /// @param[in] listener The @ref UserEarnedRewardListener to be notified when + /// user earns a reward. + Future Show(UserEarnedRewardListener* listener); + + /// Returns a @ref Future containing the status of the last call to + /// @ref Show. + Future ShowLastResult() const; + + /// Sets the @ref FullScreenContentListener for this @ref RewardedAd. + /// + /// @param[in] listener A valid @ref FullScreenContentListener to receive + /// callbacks. + void SetFullScreenContentListener(FullScreenContentListener* listener); + + /// Registers a callback to be invoked when this ad is estimated to have + /// earned money + /// + /// @param[in] listener A valid @ref PaidEventListener to receive callbacks. + void SetPaidEventListener(PaidEventListener* listener); + + /// Sets the server side verification options. + /// + /// @param[in] serverSideVerificationOptions A @ref + /// ServerSideVerificationOptions object containing custom data and a user + /// Id. + void SetServerSideVerificationOptions( + const ServerSideVerificationOptions& serverSideVerificationOptions); + + private: + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + internal::RewardedAdInternal* internal_; +}; + +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ diff --git a/ump/src/include/firebase/gma/types.h b/ump/src/include/firebase/gma/types.h new file mode 100644 index 0000000000..be568f2146 --- /dev/null +++ b/ump/src/include/firebase/gma/types.h @@ -0,0 +1,959 @@ +/* + * Copyright 2021 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ + +#include +#include +#include +#include +#include + +#include "firebase/future.h" +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +extern "C" { +#include +} // extern "C" +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +namespace firebase { +namespace gma { + +struct AdErrorInternal; +struct AdapterResponseInfoInternal; +struct BoundingBox; +struct ResponseInfoInternal; + +class AdapterResponseInfo; +class AdViewBoundingBoxListener; +class GmaInternal; +class AdView; +class InterstitialAd; +class PaidEventListener; +class ResponseInfo; + +namespace internal { +class AdViewInternal; +} + +/// This is a platform specific datatype that is required to create +/// a Google Mobile Ads ad. +/// +/// The following defines the datatype on each platform: +///
    +///
  • Android: A `jobject` which references an Android Activity.
  • +///
  • iOS: An `id` which references an iOS UIView.
  • +///
+#if FIREBASE_PLATFORM_ANDROID +/// An Android Activity from Java. +typedef jobject AdParent; +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +/// A pointer to an iOS UIView. +typedef id AdParent; +#else +/// A void pointer for stub classes. +typedef void* AdParent; +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +/// Error codes returned by Future::error(). +enum AdErrorCode { + /// Call completed successfully. + kAdErrorCodeNone, + /// The ad has not been fully initialized. + kAdErrorCodeUninitialized, + /// The ad is already initialized (repeat call). + kAdErrorCodeAlreadyInitialized, + /// A call has failed because an ad is currently loading. + kAdErrorCodeLoadInProgress, + /// A call to load an ad has failed due to an internal SDK error. + kAdErrorCodeInternalError, + /// A call to load an ad has failed due to an invalid request. + kAdErrorCodeInvalidRequest, + /// A call to load an ad has failed due to a network error. + kAdErrorCodeNetworkError, + /// A call to load an ad has failed because no ad was available to serve. + kAdErrorCodeNoFill, + /// An attempt has been made to show an ad on an Android Activity that has + /// no window token (such as one that's not done initializing). + kAdErrorCodeNoWindowToken, + /// An attempt to load an Ad Network extras class for an ad request has + /// failed. + kAdErrorCodeAdNetworkClassLoadError, + /// The ad server experienced a failure processing the request. + kAdErrorCodeServerError, + /// The current device’s OS is below the minimum required version. + kAdErrorCodeOSVersionTooLow, + /// The request was unable to be loaded before being timed out. + kAdErrorCodeTimeout, + /// Will not send request because the interstitial object has already been + /// used. + kAdErrorCodeInterstitialAlreadyUsed, + /// The mediation response was invalid. + kAdErrorCodeMediationDataError, + /// Error finding or creating a mediation ad network adapter. + kAdErrorCodeMediationAdapterError, + /// Attempting to pass an invalid ad size to an adapter. + kAdErrorCodeMediationInvalidAdSize, + /// Invalid argument error. + kAdErrorCodeInvalidArgument, + /// Received invalid response. + kAdErrorCodeReceivedInvalidResponse, + /// Will not send a request because the rewarded ad object has already been + /// used. + kAdErrorCodeRewardedAdAlreadyUsed, + /// A mediation ad network adapter received an ad request, but did not fill. + /// The adapter’s error is included as an underlyingError. + kAdErrorCodeMediationNoFill, + /// Will not send request because the ad object has already been used. + kAdErrorCodeAdAlreadyUsed, + /// Will not send request because the application identifier is missing. + kAdErrorCodeApplicationIdentifierMissing, + /// Android Ad String is invalid. + kAdErrorCodeInvalidAdString, + /// The ad can not be shown when app is not in the foreground. + kAdErrorCodeAppNotInForeground, + /// A mediation adapter failed to show the ad. + kAdErrorCodeMediationShowError, + /// The ad is not ready to be shown. + kAdErrorCodeAdNotReady, + /// Ad is too large for the scene. + kAdErrorCodeAdTooLarge, + /// Attempted to present ad from a non-main thread. This is an internal + /// error which should be reported to support if encountered. + kAdErrorCodeNotMainThread, + /// A debug operation failed because the device is not in test mode. + kAdErrorCodeNotInTestMode, + /// An attempt to load the Ad Inspector failed. + kAdErrorCodeInspectorFailedToLoad, + /// The request to show the Ad Inspector failed because it's already open. + kAdErrorCodeInsepctorAlreadyOpen, + /// Error processing image url. + kAdErrorCodeImageUrlMalformed, + /// Fallback error for any unidentified cases. + kAdErrorCodeUnknown, +}; + +#if !defined(DOXYGEN) +/// Format of the ad being requested. Currently used only for internal updates. +enum AdFormat { + /// App open ad format. + kAdFormatAppOpen, + /// Banner ad format. + kAdFormatBanner, + /// Interstitial ad format. + kAdFormatInterstitial, + /// Native ad format. + kAdFormatNative, + /// Rewarded ad format. + kAdFormatRewarded, + /// Rewarded interstitial ad format. + kAdFormatRewardedInterstitial, +}; +#endif // !defined(DOXYGEN) + +/// A listener for receiving notifications during the lifecycle of a BannerAd. +class AdListener { + public: + virtual ~AdListener(); + + /// Called when a click is recorded for an ad. + virtual void OnAdClicked() {} + + /// Called when the user is about to return to the application after clicking + /// on an ad. + virtual void OnAdClosed() {} + + /// Called when an impression is recorded for an ad. + virtual void OnAdImpression() {} + + /// Called when an ad opens an overlay that covers the screen. + virtual void OnAdOpened() {} +}; + +/// Information about why an ad operation failed. +class AdError { + public: + /// Default Constructor. + AdError(); + + /// Copy Constructor. + AdError(const AdError& ad_error); + + /// Destructor. + virtual ~AdError(); + + /// Assignment operator. + AdError& operator=(const AdError& obj); + + /// Retrieves an AdError which represents the cause of this error. + /// + /// @return a pointer to an adError which represents the cause of this + /// AdError. If there was no cause then nullptr is returned. + std::unique_ptr GetCause() const; + + /// Gets the error's code. + AdErrorCode code() const; + + /// Gets the domain of the error. + const std::string& domain() const; + + /// Gets the message describing the error. + const std::string& message() const; + + /// Gets the ResponseInfo if an error occurred during a loadAd operation. + /// The ResponseInfo will have empty fields if this AdError does not + /// represent an error stemming from a load ad operation. + const ResponseInfo& response_info() const; + + /// Returns a log friendly string version of this object. + virtual const std::string& ToString() const; + + /// A domain string which represents an undefined error domain. + /// + /// The GMA SDK returns this domain for domain() method invocations when + /// converting error information from legacy mediation adapter callbacks. + static const char* const kUndefinedDomain; + + private: + friend class AdapterResponseInfo; + friend class GmaInternal; + friend class AdView; + friend class InterstitialAd; + + /// Constructor used when building results in Ad event callbacks. + explicit AdError(const AdErrorInternal& ad_error_internal); + + // Collection of response from adapters if this Result is due to a loadAd + // operation. + ResponseInfo* response_info_; + + // An internal, platform-specific implementation object that this class uses + // to interact with the Google Mobile Ads SDKs for iOS and Android. + AdErrorInternal* internal_; +}; + +/// Information about an ad response. +class ResponseInfo { + public: + /// Constructor creates an uninitialized ResponseInfo. + ResponseInfo(); + + /// Gets the AdapterResponseInfo objects for the ad response. + /// + /// @return a vector of AdapterResponseInfo objects containing metadata for + /// each adapter included in the ad response. + const std::vector& adapter_responses() const { + return adapter_responses_; + } + + /// A class name that identifies the ad network that returned the ad. + /// Returns an empty string if the ad failed to load. + const std::string& mediation_adapter_class_name() const { + return mediation_adapter_class_name_; + } + + /// Gets the response ID string for the loaded ad. Returns an empty + /// string if the ad fails to load. + const std::string& response_id() const { return response_id_; } + + /// Gets a log friendly string version of this object. + const std::string& ToString() const { return to_string_; } + + private: + friend class AdError; + friend class GmaInternal; + + explicit ResponseInfo(const ResponseInfoInternal& internal); + + std::vector adapter_responses_; + std::string mediation_adapter_class_name_; + std::string response_id_; + std::string to_string_; +}; + +/// Information about the result of an ad operation. +class AdResult { + public: + /// Default Constructor. + AdResult(); + + /// Constructor. + explicit AdResult(const AdError& ad_error); + + /// Destructor. + virtual ~AdResult(); + + /// Returns true if the operation was successful. + bool is_successful() const; + + /// An object representing an error which occurred during an ad operation. + /// If the @ref AdResult::is_successful() returned true, then the + /// @ref AdError object returned via this method will contain no contextual + /// information. + const AdError& ad_error() const; + + /// For debugging and logging purposes, successfully loaded ads provide a + /// ResponseInfo object which contains information about the adapter which + /// loaded the ad. If the ad failed to load then the object returned from + /// this method will have default values. Information about the error + /// should be retrieved via @ref AdResult::ad_error() instead. + const ResponseInfo& response_info() const; + + private: + friend class GmaInternal; + + /// Constructor invoked upon successful ad load. This contains response + /// information from the adapter which loaded the ad. + explicit AdResult(const ResponseInfo& response_info); + + /// Denotes if the @ref AdResult represents a success or an error. + bool is_successful_; + + /// Information about the error. Will be a default-constructed @ref AdError + /// if this result represents a success. + AdError ad_error_; + + /// Information from the adapter which loaded the ad. + ResponseInfo response_info_; +}; + +/// A snapshot of a mediation adapter's initialization status. +class AdapterStatus { + public: + AdapterStatus() : is_initialized_(false), latency_(0) {} + + /// Detailed description of the status. + /// + /// This method should only be used for informational purposes, such as + /// logging. Use @ref is_initialized to make logical decisions regarding an + /// adapter's status. + const std::string& description() const { return description_; } + + /// Returns the adapter's initialization state. + bool is_initialized() const { return is_initialized_; } + + /// The adapter's initialization latency in milliseconds. + /// 0 if initialization has not yet ended. + int latency() const { return latency_; } + +#if !defined(DOXYGEN) + // Equality operator for testing. + bool operator==(const AdapterStatus& rhs) const { + return (description() == rhs.description() && + is_initialized() == rhs.is_initialized() && + latency() == rhs.latency()); + } +#endif // !defined(DOXYGEN) + + private: + friend class GmaInternal; + std::string description_; + bool is_initialized_; + int latency_; +}; + +/// An immutable snapshot of the GMA SDK’s initialization status, categorized +/// by mediation adapter. +class AdapterInitializationStatus { + public: + /// Initialization status of each known ad network, keyed by its adapter's + /// class name. + std::map GetAdapterStatusMap() const { + return adapter_status_map_; + } +#if !defined(DOXYGEN) + // Equality operator for testing. + bool operator==(const AdapterInitializationStatus& rhs) const { + return (GetAdapterStatusMap() == rhs.GetAdapterStatusMap()); + } +#endif // !defined(DOXYGEN) + + private: + friend class GmaInternal; + std::map adapter_status_map_; +}; + +/// Listener to be invoked when the Ad Inspector has been closed. +class AdInspectorClosedListener { + public: + virtual ~AdInspectorClosedListener(); + + /// Called when the user clicked the ad. The AdResult contains the status of + /// the operation, including details of the error if one occurred. + virtual void OnAdInspectorClosed(const AdResult& ad_result) = 0; +}; + +/// @brief Response information for an individual ad network contained within +/// a @ref ResponseInfo object. +class AdapterResponseInfo { + public: + /// Destructor + ~AdapterResponseInfo(); + + /// @brief Information about the result including whether an error + /// occurred, and any contextual information about that error. + /// + /// @return the error that occurred while rendering the ad. If no error + /// occurred then the AdResult's successful method will return true. + AdResult ad_result() const { return ad_result_; } + + /// Returns a string representation of a class name that identifies the ad + /// network adapter. + const std::string& adapter_class_name() const { return adapter_class_name_; } + + /// Amount of time the ad network spent loading an ad. + /// + /// @return number of milliseconds the network spent loading an ad. This value + /// is 0 if the network did not make a load attempt. + int64_t latency_in_millis() const { return latency_; } + + /// A log friendly string version of this object. + const std::string& ToString() const { return to_string_; } + + private: + friend class ResponseInfo; + + /// Constructs an Adapter Response Info Object. + explicit AdapterResponseInfo(const AdapterResponseInfoInternal& internal); + + AdResult ad_result_; + std::string adapter_class_name_; + int64_t latency_; + std::string to_string_; +}; + +/// The size of a banner ad. +class AdSize { + public: + /// Denotes the orientation of the AdSize. + enum Orientation { + /// AdSize should reflect the current orientation of the device. + kOrientationCurrent = 0, + + /// AdSize will be adaptively formatted in Landscape mode. + kOrientationLandscape, + + /// AdSize will be adaptively formatted in Portrait mode. + kOrientationPortrait + }; + + /// Denotes the type size object that the @ref AdSize represents. + enum Type { + /// The standard AdSize type of a set height and width. + kTypeStandard = 0, + + /// An adaptive size anchored to a portion of the screen. + kTypeAnchoredAdaptive, + + /// An adaptive size intended to be embedded in scrollable content. + kTypeInlineAdaptive, + }; + + /// Mobile Marketing Association (MMA) banner ad size (320x50 + /// density-independent pixels). + static const AdSize kBanner; + + /// Interactive Advertising Bureau (IAB) full banner ad size + /// (468x60 density-independent pixels). + static const AdSize kFullBanner; + + /// Taller version of kBanner. Typically 320x100. + static const AdSize kLargeBanner; + + /// Interactive Advertising Bureau (IAB) leaderboard ad size + /// (728x90 density-independent pixels). + static const AdSize kLeaderboard; + + /// Interactive Advertising Bureau (IAB) medium rectangle ad size + /// (300x250 density-independent pixels). + static const AdSize kMediumRectangle; + + /// Creates a new AdSize. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// @param[in] height The height of the ad in density-independent pixels. + AdSize(uint32_t width, uint32_t height); + + /// @brief Creates an AdSize with the given width and a Google-optimized + /// height to create a banner ad in landscape mode. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a Google-optimized height + /// to create a banner ad. The size returned will have an aspect ratio + /// similar to BANNER, suitable for anchoring near the top or bottom of + /// your app. The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetLandscapeAnchoredAdaptiveBannerAdSize(uint32_t width); + + /// @brief Creates an AdSize with the given width and a Google-optimized + /// height to create a banner ad in portrait mode. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a Google-optimized height + /// to create a banner ad. The size returned will have an aspect ratio + /// similar to BANNER, suitable for anchoring near the top or bottom + /// of your app. The exact size of the ad returned can be retrieved by + /// calling @ref AdView::ad_size once the ad has been loaded. + static AdSize GetPortraitAnchoredAdaptiveBannerAdSize(uint32_t width); + + /// @brief Creates an AdSize with the given width and a Google-optimized + /// height to create a banner ad given the current orientation. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a Google-optimized height + /// to create a banner ad. The size returned will have an aspect ratio + /// similar to AdSize, suitable for anchoring near the top or bottom of + /// your app. The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetCurrentOrientationAnchoredAdaptiveBannerAdSize( + uint32_t width); + + /// @brief This ad size is most suitable for banner ads given a maximum + /// height. + /// + /// This AdSize allows Google servers to choose an optimal ad size with + /// a height less than or equal to the max height given in + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// @param[in] max_height The maximum height that a loaded ad will have. Must + /// be + /// at least 32 dp, but a maxHeight of 50 dp or higher is recommended. + /// + /// @return an AdSize with the given width and a height that is always 0. + /// The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetInlineAdaptiveBannerAdSize(int width, int max_height); + + /// @brief Creates an AdSize with the given width and the device’s + /// landscape height. + /// + /// This ad size allows Google servers to choose an optimal ad size with + /// a height less than or equal to the height of the screen in landscape + /// orientation. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a height that is always 0. + /// The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetLandscapeInlineAdaptiveBannerAdSize(int width); + + /// @brief Creates an AdSize with the given width and the device’s + /// portrait height. + /// + /// This ad size allows Google servers to choose an optimal ad size with + /// a height less than or equal to the height of the screen in portrait + /// orientation. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a height that is always 0. + /// The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetPortraitInlineAdaptiveBannerAdSize(int width); + + /// @brief A convenience method to return an inline adaptive banner ad size + /// given the current interface orientation. + /// + /// This AdSize allows Google servers to choose an optimal ad size with a + /// height less than or equal to the height of the screen in the requested + /// orientation. + /// + /// @param[in] width The width of the ad in density-independent pixels. + /// + /// @return an AdSize with the given width and a height that is always 0. + /// The exact size of the ad returned can be retrieved by calling + /// @ref AdView::ad_size once the ad has been loaded. + static AdSize GetCurrentOrientationInlineAdaptiveBannerAdSize(int width); + + /// Comparison operator. + /// + /// @return true if `rhs` refers to the same AdSize as `this`. + bool operator==(const AdSize& rhs) const; + + /// Comparison operator. + /// + /// @return true if `rhs` refers to a different AdSize as `this`. + bool operator!=(const AdSize& rhs) const; + + /// The width of the region represented by this AdSize. Value is in + /// density-independent pixels. + uint32_t width() const { return width_; } + + /// The height of the region represented by this AdSize. Value is in + /// density-independent pixels. + uint32_t height() const { return height_; } + + /// The AdSize orientation. + Orientation orientation() const { return orientation_; } + + /// The AdSize type, either standard size or adaptive. + Type type() const { return type_; } + + private: + friend class firebase::gma::internal::AdViewInternal; + + /// Returns an Anchor Adaptive AdSize Object given a width and orientation. + static AdSize GetAnchoredAdaptiveBannerAdSize(uint32_t width, + Orientation orientation); + + /// Returns true if the AdSize parameter is equivalient to this AdSize object. + bool is_equal(const AdSize& ad_size) const; + + /// Denotes the orientation for anchored adaptive AdSize objects. + Orientation orientation_; + + /// Advertisement width in platform-indepenent pixels. + uint32_t width_; + + /// Advertisement width in platform-indepenent pixels. + uint32_t height_; + + /// The type of AdSize (standard or adaptive) + Type type_; +}; + +/// Contains targeting information used to fetch an ad. +class AdRequest { + public: + /// Creates an @ref AdRequest with no custom configuration. + AdRequest(); + + /// Creates an @ref AdRequest with the optional content URL. + /// + /// When requesting an ad, apps may pass the URL of the content they are + /// serving. This enables keyword targeting to match the ad with the content. + /// + /// The URL is ignored if null or the number of characters exceeds 512. + /// + /// @param[in] content_url the url of the content being viewed. + explicit AdRequest(const char* content_url); + + ~AdRequest(); + + /// The content URL targeting information. + /// + /// @return the content URL for the @ref AdRequest. The string will be empty + /// if no content URL has been configured. + const std::string& content_url() const { return content_url_; } + + /// A Map of adapter class names to their collection of extra parameters, as + /// configured via @ref add_extra. + const std::map >& extras() + const { + return extras_; + } + + /// Keywords which will help GMA to provide targeted ads, as added by + /// @ref add_keyword. + const std::unordered_set& keywords() const { return keywords_; } + + /// Returns the set of neighboring content URLs or an empty set if no URLs + /// were set via @ref add_neighboring_content_urls(). + const std::unordered_set& neighboring_content_urls() const { + return neighboring_content_urls_; + } + + /// Add a network extra for the associated ad mediation adapter. + /// + /// Appends an extra to the corresponding list of extras for the ad mediation + /// adapter. Each ad mediation adapter can have multiple extra strings. + /// + /// @param[in] adapter_class_name the class name of the ad mediation adapter + /// for which to add the extra. + /// @param[in] extra_key a key which will be passed to the corresponding ad + /// mediation adapter. + /// @param[in] extra_value the value associated with extra_key. + void add_extra(const char* adapter_class_name, const char* extra_key, + const char* extra_value); + + /// Adds a keyword for targeting purposes. + /// + /// Multiple keywords may be added via repeated invocations of this method. + /// + /// @param[in] keyword a string that GMA will use to aid in targeting ads. + void add_keyword(const char* keyword); + + /// When requesting an ad, apps may pass the URL of the content they are + /// serving. This enables keyword targeting to match the ad with the content. + /// + /// The URL is ignored if null or the number of characters exceeds 512. + /// + /// @param[in] content_url the url of the content being viewed. + void set_content_url(const char* content_url); + + /// Adds to the list of URLs which represent web content near an ad. + /// + /// Promotes brand safety and allows displayed ads to have an app level + /// rating (MA, T, PG, etc) that is more appropriate to neighboring content. + /// + /// Subsequent invocations append to the existing list. + /// + /// @param[in] neighboring_content_urls neighboring content URLs to be + /// attached to the existing neighboring content URLs. + void add_neighboring_content_urls( + const std::vector& neighboring_content_urls); + + private: + std::string content_url_; + std::map > extras_; + std::unordered_set keywords_; + std::unordered_set neighboring_content_urls_; +}; + +/// Describes a reward credited to a user for interacting with a RewardedAd. +class AdReward { + public: + /// Creates an @ref AdReward. + AdReward(const std::string& type, int64_t amount) + : amount_(amount), type_(type) {} + + /// Returns the reward amount. + int64_t amount() const { return amount_; } + + /// Returns the type of the reward. + const std::string& type() const { return type_; } + + private: + const int64_t amount_; + const std::string type_; +}; + +/// The monetary value earned from an ad. +class AdValue { + public: + /// Allowed constants for @ref precision_type(). + enum PrecisionType { + /// An ad value with unknown precision. + kAdValuePrecisionUnknown = 0, + /// An ad value estimated from aggregated data. + kAdValuePrecisionEstimated, + /// A publisher-provided ad value, such as manual CPMs in a mediation group. + kAdValuePrecisionPublisherProvided = 2, + /// The precise value paid for this ad. + kAdValuePrecisionPrecise = 3 + }; + + /// Constructor + AdValue(const char* currency_code, PrecisionType precision_type, + int64_t value_micros) + : currency_code_(currency_code), + precision_type_(precision_type), + value_micros_(value_micros) {} + + /// The value's ISO 4217 currency code. + const std::string& currency_code() const { return currency_code_; } + + /// The precision of the reported ad value. + PrecisionType precision_type() const { return precision_type_; } + + /// The ad's value in micro-units, where 1,000,000 micro-units equal one + /// unit of the currency. + int64_t value_micros() const { return value_micros_; } + + private: + const std::string currency_code_; + const PrecisionType precision_type_; + const int64_t value_micros_; +}; + +/// @brief Listener to be invoked when ads show and dismiss full screen content, +/// such as a fullscreen ad experience or an in-app browser. +class FullScreenContentListener { + public: + virtual ~FullScreenContentListener(); + + /// Called when the user clicked the ad. + virtual void OnAdClicked() {} + + /// Called when the ad dismissed full screen content. + virtual void OnAdDismissedFullScreenContent() {} + + /// Called when the ad failed to show full screen content. + /// + /// @param[in] ad_error An object containing detailed information + /// about the error. + virtual void OnAdFailedToShowFullScreenContent(const AdError& ad_error) {} + + /// Called when an impression is recorded for an ad. + virtual void OnAdImpression() {} + + /// Called when the ad showed the full screen content. + virtual void OnAdShowedFullScreenContent() {} +}; + +/// Listener to be invoked when ads have been estimated to earn money. +class PaidEventListener { + public: + virtual ~PaidEventListener(); + + /// Called when an ad is estimated to have earned money. + virtual void OnPaidEvent(const AdValue& value) {} +}; + +/// @brief Global configuration that will be used for every @ref AdRequest. +/// Set the configuration via @ref SetRequestConfiguration. +struct RequestConfiguration { + /// A maximum ad content rating, which may be configured via + /// @ref max_ad_content_rating. + enum MaxAdContentRating { + /// No content rating has been specified. + kMaxAdContentRatingUnspecified = -1, + + /// Content suitable for general audiences, including families. + kMaxAdContentRatingG, + + /// Content suitable only for mature audiences. + kMaxAdContentRatingMA, + + /// Content suitable for most audiences with parental guidance. + kMaxAdContentRatingPG, + + /// Content suitable for teen and older audiences. + kMaxAdContentRatingT + }; + + /// Specify whether you would like your app to be treated as child-directed + /// for purposes of the Children’s Online Privacy Protection Act (COPPA). + /// Values defined here may be configured via + /// @ref tag_for_child_directed_treatment. + enum TagForChildDirectedTreatment { + /// Indicates that ad requests will include no indication of how you would + /// like your app treated with respect to COPPA. + kChildDirectedTreatmentUnspecified = -1, + + /// Indicates that your app should not be treated as child-directed for + /// purposes of the Children’s Online Privacy Protection Act (COPPA). + kChildDirectedTreatmentFalse, + + /// Indicates that your app should be treated as child-directed for purposes + /// of the Children’s Online Privacy Protection Act (COPPA). + kChildDirectedTreatmentTrue + }; + + /// Configuration values to mark your app to receive treatment for users in + /// the European Economic Area (EEA) under the age of consent. Values defined + /// here should be configured via @ref tag_for_under_age_of_consent. + enum TagForUnderAgeOfConsent { + /// Indicates that the publisher has not specified whether the ad request + /// should receive treatment for users in the European Economic Area (EEA) + /// under the age of consent. + kUnderAgeOfConsentUnspecified = -1, + + /// Indicates the publisher specified that the ad request should not receive + /// treatment for users in the European Economic Area (EEA) under the age of + /// consent. + kUnderAgeOfConsentFalse, + + /// Indicates the publisher specified that the ad request should receive + /// treatment for users in the European Economic Area (EEA) under the age of + /// consent. + kUnderAgeOfConsentTrue + }; + + /// Sets a maximum ad content rating. GMA ads returned for your app will + /// have a content rating at or below that level. + MaxAdContentRating max_ad_content_rating; + + /// @brief Allows you to specify whether you would like your app + /// to be treated as child-directed for purposes of the Children’s Online + /// Privacy Protection Act (COPPA) - + /// http://business.ftc.gov/privacy-and-security/childrens-privacy. + /// + /// If you set this value to + /// RequestConfiguration.kChildDirectedTreatmentTrue, you will indicate + /// that your app should be treated as child-directed for purposes of the + /// Children’s Online Privacy Protection Act (COPPA). + /// + /// If you set this value to + /// RequestConfiguration.kChildDirectedTreatmentFalse, you will indicate + /// that your app should not be treated as child-directed for purposes of the + /// Children’s Online Privacy Protection Act (COPPA). + /// + /// If you do not set this value, or set this value to + /// RequestConfiguration.kChildDirectedTreatmentUnspecified, ad requests will + /// include no indication of how you would like your app treated with respect + /// to COPPA. + /// + /// By setting this value, you certify that this notification is accurate and + /// you are authorized to act on behalf of the owner of the app. You + /// understand that abuse of this setting may result in termination of your + /// Google account. + /// + /// @note: it may take some time for this designation to be fully implemented + /// in applicable Google services. + /// + TagForChildDirectedTreatment tag_for_child_directed_treatment; + + /// This value allows you to mark your app to receive treatment for users in + /// the European Economic Area (EEA) under the age of consent. This feature is + /// designed to help facilitate compliance with the General Data Protection + /// Regulation (GDPR). Note that you may have other legal obligations under + /// GDPR. Please review the European Union's guidance and consult with your + /// own legal counsel. Please remember that Google's tools are designed to + /// facilitate compliance and do not relieve any particular publisher of its + /// obligations under the law. + /// + /// When using this feature, a Tag For Users under the Age of Consent in + /// Europe (TFUA) parameter will be included in all ad requests. This + /// parameter disables personalized advertising, including remarketing, for + /// that specific ad request. It also disables requests to third-party ad + /// vendors, such as ad measurement pixels and third-party ad servers. + /// + /// If you set this value to RequestConfiguration.kUnderAgeOfConsentTrue, you + /// will indicate that you want your app to be handled in a manner suitable + /// for users under the age of consent. + /// + /// If you set this value to RequestConfiguration.kUnderAgeOfConsentFalse, + /// you will indicate that you don't want your app to be handled in a manner + /// suitable for users under the age of consent. + /// + /// If you do not set this value, or set this value to + /// kUnderAgeOfConsentUnspecified, your app will include no indication of how + /// you would like your app to be handled in a manner suitable for users under + /// the age of consent. + TagForUnderAgeOfConsent tag_for_under_age_of_consent; + + /// Sets a list of test device IDs corresponding to test devices which will + /// always request test ads. + std::vector test_device_ids; +}; + +/// Listener to be invoked when the user earned a reward. +class UserEarnedRewardListener { + public: + virtual ~UserEarnedRewardListener(); + /// Called when the user earned a reward. The app is responsible for + /// crediting the user with the reward. + /// + /// @param[in] reward the @ref AdReward that should be granted to the user. + virtual void OnUserEarnedReward(const AdReward& reward) {} +}; + +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ diff --git a/ump/src/include/firebase/gma/ump.h b/ump/src/include/firebase/gma/ump.h new file mode 100644 index 0000000000..26e90d630b --- /dev/null +++ b/ump/src/include/firebase/gma/ump.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ + +#include "firebase/gma/ump/consent_info.h" +#include "firebase/gma/ump/types.h" + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ diff --git a/ump/src/include/firebase/gma/ump/consent_info.h b/ump/src/include/firebase/gma/ump/consent_info.h new file mode 100644 index 0000000000..8f69918ed0 --- /dev/null +++ b/ump/src/include/firebase/gma/ump/consent_info.h @@ -0,0 +1,249 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ + +#include "firebase/app.h" +#include "firebase/future.h" +#include "firebase/gma/ump/types.h" +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#endif // FIREBASE_PLATFORM_ANDROID + +namespace firebase { +namespace gma { +/// @brief API for User Messaging Platform. +/// +/// The User Messaging Platform (UMP) SDK is Google’s option to handle user +/// privacy and consent in mobile apps. +namespace ump { + +namespace internal { +// Forward declaration for platform-specific data, implemented in each library. +class ConsentInfoInternal; +} // namespace internal + +/// @brief Consent Information class for the User Messaging Platform SDK. +/// +/// The User Messaging Platform (UMP) SDK is Google’s option to handle user +/// privacy and consent in mobile apps. +/// +/// This class contains all of the methods necessary for obtaining +/// consent from the user. +class ConsentInfo { + public: + /// Shut down the User Messaging Platform Consent SDK. + ~ConsentInfo(); + + /// Initializes the User Messaging Platform Consent SDK. + /// + /// @param[in] app Any Firebase App instance. + /// + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency on Android if there are Android + /// dependencies missing. + /// + /// @return A pointer to the ConsentInfo instance if UMP was successfully + /// initialized, nullptr otherwise. Each call to GetInstance() will return the + /// same pointer; when you are finished using the SDK, you can delete the + /// pointer and the UMP SDK will shut down. + static ConsentInfo* GetInstance(const ::firebase::App& app, + InitResult* init_result_out = nullptr); + +#if FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + /// Initializes the User Messaging Platform Consent SDK without Firebase for + /// Android. + /// + /// The arguments to GetInstance() are platform-specific so the caller must + /// do something like this: + /// @code + /// #if defined(__ANDROID__) + /// consent_info = firebase::gma::ump::ConsentInfo::GetInstance(jni_env, + /// activity); + /// #else + /// consent_info = firebase::gma::ump::GetInstance(); + /// #endif + /// @endcode + /// + /// @param[in] jni_env JNIEnv pointer. + /// @param[in] activity Activity used to start the application. + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency on Android if there are Android + /// dependencies missing. + /// + /// @return A pointer to the ConsentInfo instance if UMP was successfully + /// initialized, nullptr otherwise. Each call to GetInstance() will return the + /// same pointer; when you are finished using the SDK, you can delete the + /// pointer and the UMP SDK will shut down. + static ConsentInfo* GetInstance(JNIEnv* jni_env, jobject activity, + InitResult* init_result_out = nullptr); + +#if !defined(DOXYGEN) + // On Android, this convenience function exists so you can easily get the + // existing ConsentInfo instance after it's first initialized. Returns nullptr + // if no instance has been created yet; make sure you have called + // GetInstance(JNIEnv*, jobject) first. + static ConsentInfo* GetInstance(); +#endif // defined(DOXYGEN) +#endif // FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + +#if !FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + /// Initializes User Messaging Platform for iOS without Firebase. + /// + /// @param[out] init_result_out Optional: If provided, write the basic init + /// result here. kInitResultSuccess if initialization succeeded, or + /// kInitResultFailedMissingDependency if a dependency is missing. On iOS, + /// this will always return kInitResultSuccess, as missing dependencies would + /// have caused a linker error at build time. + /// + /// @return A pointer to the ConsentInfo instance. Each call to GetInstance() + /// will return the same pointer; when you are finished using the SDK, you can + /// delete the pointer, and the UMP SDK will shut down. + /// + /// @note Once any overload of ConsentInfo::GetInstance has been called, you + /// can use this method to obtain the same instance again. + static ConsentInfo* GetInstance(InitResult* init_result_out = nullptr); +#endif // !defined(__ANDROID__) || defined(DOXYGEN) + + /// The user’s consent status. This value defaults to kConsentStatusUnknown + /// until RequestConsentInfoUpdate() is called, and defaults to the previous + /// session’s value until RequestConsentInfoUpdate() completes. + ConsentStatus GetConsentStatus(); + + /// Requests consent information update. Must be called in every app session + /// before checking the user’s consent status or loading a consent form. After + /// calling this method, GetConsentStatus() and CanRequestAds() will be + /// updated immediately to hold the consent state from the previous app + /// session, if one exists. GetConsentStatus() and CanRequestAds() may be + /// updated again immediately before the returned future is completed. + Future RequestConsentInfoUpdate(const ConsentRequestParameters& params); + + /// Get the Future from the most recent call to RequestConsentInfoUpdate(). + Future RequestConsentInfoUpdateLastResult(); + + /// Consent form status. This value defaults to kConsentFormStatusUnknown and + /// requires a call to RequestConsentInfoUpdate() to update. + ConsentFormStatus GetConsentFormStatus(); + + /// Loads a consent form. Returns an error if the consent form is unavailable + /// or cannot be loaded. + Future LoadConsentForm(); + + /// Get the Future from the most recent call to LoadConsentForm(). + Future LoadConsentFormLastResult(); + + /// Presents the full screen consent form using the given FormParent, which is + /// defined as an Activity on Android and a UIViewController on iOS. The form + /// will be dismissed and the Future will be completed after the user selects + /// an option. + /// + /// GetConsentStatus() and CanRequestAds() are updated when the returned + /// Future is completed. + /// + /// @param[in] parent A FormParent, which is an Activity object on Android and + /// a UIViewController object on iOS. + /// + /// @note You must call LoadConsentForm() and wait for it to complete before + /// calling this method. + Future ShowConsentForm(FormParent parent); + + /// Get the Future from the most recent call to ShowConsentForm(). + Future ShowConsentFormLastResult(); + + /// Loads a consent form and immediately presents it using the given + /// FormParent, if ConsentStatus is kConsentStatusRequired. The FormParent is + /// defined as an Activity on Android and a UIViewController on iOS. The + /// Future will be completed successfully after the user selects an option + /// (and the form is dismissed), or if the form is not required. The Future + /// will be completed with an error if the form fails to load or show. + /// + /// GetConsentStatus() and CanRequestAds() will be updated prior to the Future + /// being completed. + /// + /// @param[in] parent A FormParent, which is an Activity object on Android and + /// a UIViewController object on iOS. + Future LoadAndShowConsentFormIfRequired(FormParent parent); + + /// Get the Future from the most recent call to + /// LoadAndShowConsentFormIfRequired(). + Future LoadAndShowConsentFormIfRequiredLastResult(); + + /// Check whether the privacy options form needs to be displayed. + /// This is updated by RequestConsentInfoUpdate(). + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus(); + + /// If GetPrivacyOptionsRequirementStatus() is + /// kPrivacyOptionsRequirementStatusRequired, presents a privacy options form + /// from the provided FormParent, which is defined as an Activity on Android + /// and a UIViewController on iOS. + /// + /// This method should only be called in response to a user input to request a + /// privacy options form to be shown. + /// + /// The future completes when the user selects an option and dismisses the + /// form or is completed immediately with an error code if no form is + /// presented. The privacy options form is preloaded by the SDK automatically + /// when a form becomes available. If no form has been preloaded, the SDK will + /// try to load one asynchronously. + /// + /// @param[in] parent A FormParent, which is an Activity object on Android and + /// a UIViewController object on iOS. + Future ShowPrivacyOptionsForm(FormParent parent); + + /// Get the Future from the most recent call to ShowPrivacyOptionsForm(). + Future ShowPrivacyOptionsFormLastResult(); + + /// Indicates whether the app has completed the necessary steps for gathering + /// updated user consent. Returns true if RequestConsentInfoUpdate() has been + /// called and GetConsentStatus returns either kConsentStatusNotRequired or + /// kConsentStatusObtained. + bool CanRequestAds(); + + /// Clears all consent state from persistent storage. This can be used in + /// development to simulate a new installation. + void Reset(); + + private: + ConsentInfo(); +#if FIREBASE_PLATFORM_ANDROID + InitResult Initialize(JNIEnv* jni_env, jobject activity); +#else + InitResult Initialize(); +#endif // FIREBASE_PLATFORM_ANDROID + void Terminate(); + + static ConsentInfo* s_instance_; + +#if FIREBASE_PLATFORM_ANDROID + JavaVM* java_vm() { return java_vm_; } + JavaVM* java_vm_; +#endif + + // An internal, platform-specific implementation object that this class uses + // to interact with the User Messaging Platform SDKs for iOS and Android. + internal::ConsentInfoInternal* internal_; +}; + +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ diff --git a/ump/src/include/firebase/gma/ump/types.h b/ump/src/include/firebase/gma/ump/types.h new file mode 100644 index 0000000000..0684858582 --- /dev/null +++ b/ump/src/include/firebase/gma/ump/types.h @@ -0,0 +1,179 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ +#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ + +#include +#include +#include + +#include "firebase/internal/platform.h" + +#if FIREBASE_PLATFORM_ANDROID +#include +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +extern "C" { +#include +} // extern "C" +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +namespace firebase { +namespace gma { +namespace ump { + +/// Debug values for testing geography. +enum ConsentDebugGeography { + /// Disable geography debugging. + kConsentDebugGeographyDisabled = 0, + /// Geography appears as in EEA (European Economic Area) for debug devices. + kConsentDebugGeographyEEA, + /// Geography appears as not in EEA for debug devices. + kConsentDebugGeographyNonEEA +}; + +/// Debug settings for `ConsentInfo::RequestConsentInfoUpdate()`. These let you +/// force a specific geographic location. Be sure to include debug device IDs to +/// enable this on hardware. Debug features are always enabled for simulators. +struct ConsentDebugSettings { + /// Create a default debug setting, with debugging disabled. + ConsentDebugSettings() : debug_geography(kConsentDebugGeographyDisabled) {} + + /// The geographical location, for debugging. + ConsentDebugGeography debug_geography; + /// A list of all device IDs that are allowed to use debug settings. You can + /// obtain this from the device log after running with debug settings enabled. + std::vector debug_device_ids; +}; + +/// Parameters for the `ConsentInfo::RequestConsentInfoUpdate()` operation. +struct ConsentRequestParameters { + ConsentRequestParameters() : tag_for_under_age_of_consent(false) {} + + /// Debug settings for the consent request. + ConsentDebugSettings debug_settings; + + /// Whether the user is under the age of consent. + bool tag_for_under_age_of_consent; +}; + +/// This is a platform specific datatype that is required to show a consent form +/// on screen. +/// +/// The following defines the datatype on each platform: +///
    +///
  • Android: A `jobject` which references an Android Activity.
  • +///
  • iOS: An `id` which references an iOS UIViewController.
  • +///
+#if FIREBASE_PLATFORM_ANDROID +/// An Android Activity from Java. +typedef jobject FormParent; +#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS +/// A pointer to an iOS UIViewController. +typedef id FormParent; +#else +/// A void pointer for stub classes. +typedef void* FormParent; +#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, + // FIREBASE_PLATFORM_TVOS + +/// Consent status values. +enum ConsentStatus { + /// Unknown status, e.g. prior to calling Request, or if the request fails. + kConsentStatusUnknown = 0, + /// Consent is required, but not obtained + kConsentStatusRequired, + /// Consent is not required + kConsentStatusNotRequired, + /// Consent was required, and has been obtained + kConsentStatusObtained +}; + +/// Errors that can occur during a RequestConsentInfoUpdate operation. +enum ConsentRequestError { + /// The operation succeeded. + kConsentRequestSuccess = 0, + /// Invalid GMA App ID specified in AndroidManifest.xml or Info.plist. + kConsentRequestErrorInvalidAppId, + /// A network error occurred. + kConsentRequestErrorNetwork, + /// An internal error occurred. + kConsentRequestErrorInternal, + /// A misconfiguration exists in the UI. + kConsentRequestErrorMisconfiguration, + /// An unknown error occurred. + kConsentRequestErrorUnknown, + /// An invalid operation occurred. Try again. + kConsentRequestErrorInvalidOperation, + /// The operation is already in progress. Use + /// `ConsentInfo::RequestConsentInfoUpdateLastResult()` + /// to get the status. + kConsentRequestErrorOperationInProgress +}; + +/// Status of the consent form, whether it is available to show or not. +enum ConsentFormStatus { + /// Status is unknown. Call `ConsentInfo::RequestConsentInfoUpdate()` to + /// update this. + kConsentFormStatusUnknown = 0, + /// The consent form is unavailable. Call `ConsentInfo::LoadConsentForm()` to + /// load it. + kConsentFormStatusUnavailable, + /// The consent form is available. Call `ConsentInfo::ShowConsentForm()` to + /// display it. + kConsentFormStatusAvailable, +}; + +/// Errors when loading or showing the consent form. +enum ConsentFormError { + /// The operation succeeded. + kConsentFormSuccess = 0, + /// The load request timed out. Try again. + kConsentFormErrorTimeout, + /// An internal error occurred. + kConsentFormErrorInternal, + /// An unknown error occurred. + kConsentFormErrorUnknown, + /// The form is unavailable. + kConsentFormErrorUnavailable, + /// This form was already used. + kConsentFormErrorAlreadyUsed, + /// An invalid operation occurred. Try again. + kConsentFormErrorInvalidOperation, + /// The operation is already in progress. Call + /// `ConsentInfo::LoadConsentFormLastResult()` or + /// `ConsentInfo::ShowConsentFormLastResult()` to get the status. + kConsentFormErrorOperationInProgress +}; + +/// Whether the privacy options need to be displayed. +enum PrivacyOptionsRequirementStatus { + /// Privacy options requirement status is unknown. Call + /// `ConsentInfo::RequestConsentInfoUpdate()` to update. + kPrivacyOptionsRequirementStatusUnknown = 0, + /// Privacy options are not required to be shown. + kPrivacyOptionsRequirementStatusNotRequired, + /// Privacy options must be shown. Call + /// `ConsentInfo::ShowPrivacyOptionsForm()` to fulfil this requirement. + kPrivacyOptionsRequirementStatusRequired +}; + +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ diff --git a/ump/src/ios/.clang-format b/ump/src/ios/.clang-format new file mode 100644 index 0000000000..9d159247d5 --- /dev/null +++ b/ump/src/ios/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: false diff --git a/ump/src/ios/ump/consent_info_internal_ios.h b/ump/src/ios/ump/consent_info_internal_ios.h new file mode 100644 index 0000000000..6e860edb40 --- /dev/null +++ b/ump/src/ios/ump/consent_info_internal_ios.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ +#define FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ + +#include + +#include "firebase/internal/mutex.h" +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +class ConsentInfoInternalIos : public ConsentInfoInternal { + public: + ConsentInfoInternalIos(); + ~ConsentInfoInternalIos() override; + + ConsentStatus GetConsentStatus() override; + Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) override; + + ConsentFormStatus GetConsentFormStatus() override; + Future LoadConsentForm() override; + Future ShowConsentForm(FormParent parent) override; + + Future LoadAndShowConsentFormIfRequired(FormParent parent) override; + + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus() override; + Future ShowPrivacyOptionsForm(FormParent parent) override; + + bool CanRequestAds() override; + + void Reset() override; + + private: + static ConsentInfoInternalIos* s_instance; + static firebase::Mutex s_instance_mutex; + static unsigned int s_instance_tag; + + void SetLoadedForm(UMPConsentForm *form) { + loaded_form_ = form; + } + + UMPConsentForm *loaded_form_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ diff --git a/ump/src/ios/ump/consent_info_internal_ios.mm b/ump/src/ios/ump/consent_info_internal_ios.mm new file mode 100644 index 0000000000..c4ea199eca --- /dev/null +++ b/ump/src/ios/ump/consent_info_internal_ios.mm @@ -0,0 +1,369 @@ +/* + * 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. + */ + +#include "gma/src/ios/ump/consent_info_internal_ios.h" + +#include "app/src/assert.h" +#include "app/src/thread.h" +#include "app/src/util_ios.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +ConsentInfoInternalIos* ConsentInfoInternalIos::s_instance = nullptr; +firebase::Mutex ConsentInfoInternalIos::s_instance_mutex; +unsigned int ConsentInfoInternalIos::s_instance_tag = 0; + +// This explicitly implements the constructor for the outer class, +// ConsentInfoInternal. +ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { return new ConsentInfoInternalIos(); } + +ConsentInfoInternalIos::ConsentInfoInternalIos() : loaded_form_(nil) { + MutexLock lock(s_instance_mutex); + FIREBASE_ASSERT(s_instance == nullptr); + s_instance = this; + // Increment this with each created instance, to ensure that any leftover + // callbacks don't run if a new instance is created. + s_instance_tag++; +} + +ConsentInfoInternalIos::~ConsentInfoInternalIos() { + MutexLock lock(s_instance_mutex); + s_instance = nullptr; +} + +static ConsentRequestError CppRequestErrorFromIosRequestError(NSInteger code) { + switch (code) { + case UMPRequestErrorCodeInternal: + return kConsentRequestErrorInternal; + case UMPRequestErrorCodeInvalidAppID: + return kConsentRequestErrorInvalidAppId; + case UMPRequestErrorCodeMisconfiguration: + return kConsentRequestErrorMisconfiguration; + case UMPRequestErrorCodeNetwork: + return kConsentRequestErrorNetwork; + default: + LogWarning("GMA: Unknown UMPRequestErrorCode returned by UMP iOS SDK: %d", (int)code); + return kConsentRequestErrorUnknown; + } +} + +static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { + switch (code) { + case UMPFormErrorCodeInternal: + return kConsentFormErrorInternal; + case UMPFormErrorCodeAlreadyUsed: + return kConsentFormErrorAlreadyUsed; + case UMPFormErrorCodeUnavailable: + return kConsentFormErrorUnavailable; + case UMPFormErrorCodeTimeout: + return kConsentFormErrorTimeout; + case UMPFormErrorCodeInvalidViewController: + return kConsentFormErrorInvalidOperation; + default: + LogWarning("GMA: Unknown UMPFormErrorCode returned by UMP iOS SDK: %d", (int)code); + return kConsentFormErrorUnknown; + } +} + +Future ConsentInfoInternalIos::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + MutexLock lock(s_instance_mutex); + if (RequestConsentInfoUpdateLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentRequestErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnRequestConsentInfoUpdate); + + UMPRequestParameters* ios_parameters = [[UMPRequestParameters alloc] init]; + ios_parameters.tagForUnderAgeOfConsent = params.tag_for_under_age_of_consent ? YES : NO; + UMPDebugSettings* ios_debug_settings = [[UMPDebugSettings alloc] init]; + bool has_debug_settings = false; + + switch (params.debug_settings.debug_geography) { + case kConsentDebugGeographyEEA: + ios_debug_settings.geography = UMPDebugGeographyEEA; + has_debug_settings = true; + break; + case kConsentDebugGeographyNonEEA: + ios_debug_settings.geography = UMPDebugGeographyNotEEA; + has_debug_settings = true; + break; + case kConsentDebugGeographyDisabled: + ios_debug_settings.geography = UMPDebugGeographyDisabled; + break; + } + if (params.debug_settings.debug_device_ids.size() > 0) { + ios_debug_settings.testDeviceIdentifiers = + firebase::util::StringVectorToNSMutableArray(params.debug_settings.debug_device_ids); + has_debug_settings = true; + } + if (has_debug_settings) { + ios_parameters.debugSettings = ios_debug_settings; + } + + unsigned int callback_instance_tag; + callback_instance_tag = s_instance_tag; + + util::DispatchAsyncSafeMainQueue(^{ + MutexLock lock(s_instance_mutex); + if (!s_instance || s_instance_tag != callback_instance_tag) { + // Instance changed or was invalidated, don't call the iOS method any more. + return; + } + [UMPConsentInformation.sharedInstance + requestConsentInfoUpdateWithParameters:ios_parameters + completionHandler:^(NSError* _Nullable error) { + MutexLock lock(s_instance_mutex); + if (s_instance && s_instance_tag == callback_instance_tag) { + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, + CppRequestErrorFromIosRequestError(error.code), + error.localizedDescription.UTF8String); + } + } + }]; + }); + + return MakeFuture(futures(), handle); +} + +ConsentStatus ConsentInfoInternalIos::GetConsentStatus() { + UMPConsentStatus ios_status = UMPConsentInformation.sharedInstance.consentStatus; + switch (ios_status) { + case UMPConsentStatusNotRequired: + return kConsentStatusNotRequired; + case UMPConsentStatusRequired: + return kConsentStatusRequired; + case UMPConsentStatusObtained: + return kConsentStatusObtained; + case UMPConsentStatusUnknown: + return kConsentStatusUnknown; + default: + LogWarning("GMA: Unknown UMPConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); + return kConsentStatusUnknown; + } +} + +ConsentFormStatus ConsentInfoInternalIos::GetConsentFormStatus() { + UMPFormStatus ios_status = UMPConsentInformation.sharedInstance.formStatus; + switch (ios_status) { + case UMPFormStatusAvailable: + return kConsentFormStatusAvailable; + case UMPFormStatusUnavailable: + return kConsentFormStatusUnavailable; + case UMPFormStatusUnknown: + return kConsentFormStatusUnknown; + default: + LogWarning("GMA: Unknown UMPFormConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); + return kConsentFormStatusUnknown; + } +} + +Future ConsentInfoInternalIos::LoadConsentForm() { + MutexLock lock(s_instance_mutex); + if (LoadConsentFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); + loaded_form_ = nil; + + unsigned int callback_instance_tag; + callback_instance_tag = s_instance_tag; + + util::DispatchAsyncSafeMainQueue(^{ + MutexLock lock(s_instance_mutex); + if (!s_instance || s_instance_tag != callback_instance_tag) { + // Instance changed or was invalidated, don't call the iOS method any more. + return; + } + [UMPConsentForm + loadWithCompletionHandler:^(UMPConsentForm* _Nullable form, NSError* _Nullable error) { + MutexLock lock(s_instance_mutex); + if (s_instance && s_instance_tag == callback_instance_tag) { + if (form) { + SetLoadedForm(form); + CompleteFuture(handle, kConsentFormSuccess, "Success"); + } else if (error) { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), + error.localizedDescription.UTF8String); + } else { + CompleteFuture(handle, kConsentFormErrorUnknown, "An unknown error occurred."); + } + } + }]; + }); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalIos::ShowConsentForm(FormParent parent) { + MutexLock lock(s_instance_mutex); + if (ShowConsentFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); + + if (!loaded_form_) { + CompleteFuture(handle, kConsentFormErrorInvalidOperation, + "You must call LoadConsentForm() prior to calling ShowConsentForm()."); + } else { + unsigned int callback_instance_tag; + callback_instance_tag = s_instance_tag; + + util::DispatchAsyncSafeMainQueue(^{ + MutexLock lock(s_instance_mutex); + if (!s_instance || s_instance_tag != callback_instance_tag) { + // Instance changed or was invalidated, don't call the iOS method any more. + return; + } + [loaded_form_ presentFromViewController:parent + completionHandler:^(NSError* _Nullable error) { + MutexLock lock(s_instance_mutex); + if (s_instance && s_instance_tag == callback_instance_tag) { + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, CppFormErrorFromIosFormError(error.code), + error.localizedDescription.UTF8String); + } + } + }]; + }); + } + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalIos::LoadAndShowConsentFormIfRequired(FormParent parent) { + MutexLock lock(s_instance_mutex); + if (LoadAndShowConsentFormIfRequiredLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); + + unsigned int callback_instance_tag; + callback_instance_tag = s_instance_tag; + + util::DispatchAsyncSafeMainQueue(^{ + MutexLock lock(s_instance_mutex); + if (!s_instance || s_instance_tag != callback_instance_tag) { + // Instance changed or was invalidated, don't call the iOS method any more. + return; + } + [UMPConsentForm + loadAndPresentIfRequiredFromViewController:parent + completionHandler:^(NSError* _Nullable error) { + MutexLock lock(s_instance_mutex); + if (s_instance && s_instance_tag == callback_instance_tag) { + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, + CppFormErrorFromIosFormError(error.code), + error.localizedDescription.UTF8String); + } + } + }]; + }); + return MakeFuture(futures(), handle); +} + +PrivacyOptionsRequirementStatus ConsentInfoInternalIos::GetPrivacyOptionsRequirementStatus() { + UMPPrivacyOptionsRequirementStatus ios_status = + UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus; + switch (ios_status) { + case UMPPrivacyOptionsRequirementStatusRequired: + return kPrivacyOptionsRequirementStatusRequired; + case UMPPrivacyOptionsRequirementStatusNotRequired: + return kPrivacyOptionsRequirementStatusNotRequired; + case UMPPrivacyOptionsRequirementStatusUnknown: + return kPrivacyOptionsRequirementStatusUnknown; + default: + LogWarning("GMA: Unknown UMPPrivacyOptionsRequirementStatus returned by UMP iOS SDK: %d", + (int)ios_status); + return kPrivacyOptionsRequirementStatusUnknown; + } +} + +Future ConsentInfoInternalIos::ShowPrivacyOptionsForm(FormParent parent) { + MutexLock lock(s_instance_mutex); + if (ShowPrivacyOptionsFormLastResult().status() == kFutureStatusPending) { + // This operation is already in progress. + // Return a future with an error - this will not override the Fn entry. + SafeFutureHandle error_handle = CreateFuture(); + CompleteFuture(error_handle, kConsentFormErrorOperationInProgress); + return MakeFuture(futures(), error_handle); + } + + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); + unsigned int callback_instance_tag; + callback_instance_tag = s_instance_tag; + + util::DispatchAsyncSafeMainQueue(^{ + MutexLock lock(s_instance_mutex); + if (!s_instance || s_instance_tag != callback_instance_tag) { + // Instance changed or was invalidated, don't call the iOS method any more. + return; + } + [UMPConsentForm + presentPrivacyOptionsFormFromViewController:parent + completionHandler:^(NSError* _Nullable error) { + MutexLock lock(s_instance_mutex); + if (s_instance && s_instance_tag == callback_instance_tag) { + if (!error) { + CompleteFuture(handle, kConsentRequestSuccess); + } else { + CompleteFuture(handle, + CppFormErrorFromIosFormError(error.code), + error.localizedDescription.UTF8String); + } + } + }]; + }); + return MakeFuture(futures(), handle); +} + +bool ConsentInfoInternalIos::CanRequestAds() { + return (UMPConsentInformation.sharedInstance.canRequestAds == YES ? true : false); +} + +void ConsentInfoInternalIos::Reset() { [UMPConsentInformation.sharedInstance reset]; } + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/ump/src/stub/ump/consent_info_internal_stub.cc b/ump/src/stub/ump/consent_info_internal_stub.cc new file mode 100644 index 0000000000..6bc92ec08c --- /dev/null +++ b/ump/src/stub/ump/consent_info_internal_stub.cc @@ -0,0 +1,167 @@ +/* + * 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. + */ + +#include "gma/src/stub/ump/consent_info_internal_stub.h" + +#include "app/src/thread.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// This explicitly implements the constructor for the outer class, +// ConsentInfoInternal. +ConsentInfoInternal* ConsentInfoInternal::CreateInstance() { + return new ConsentInfoInternalStub(); +} + +ConsentInfoInternalStub::ConsentInfoInternalStub() + : consent_status_(kConsentStatusUnknown), + consent_form_status_(kConsentFormStatusUnknown), + privacy_options_requirement_status_( + kPrivacyOptionsRequirementStatusUnknown), + under_age_of_consent_(false), + debug_geo_(kConsentDebugGeographyDisabled) {} + +ConsentInfoInternalStub::~ConsentInfoInternalStub() {} + +Future ConsentInfoInternalStub::RequestConsentInfoUpdate( + const ConsentRequestParameters& params) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnRequestConsentInfoUpdate); + + // See the header file for an explanation of these default settings. + ConsentStatus new_consent_status = kConsentStatusObtained; + PrivacyOptionsRequirementStatus new_privacy_req = + kPrivacyOptionsRequirementStatusNotRequired; + // Simulate consent status based on debug geo. + if (params.debug_settings.debug_geography == kConsentDebugGeographyEEA) { + new_consent_status = kConsentStatusRequired; + } else if (params.debug_settings.debug_geography == + kConsentDebugGeographyNonEEA) { + new_consent_status = kConsentStatusNotRequired; + } + + consent_status_ = new_consent_status; + under_age_of_consent_ = params.tag_for_under_age_of_consent; + consent_form_status_ = + (under_age_of_consent_ || consent_status_ != kConsentStatusRequired) + ? kConsentFormStatusUnavailable + : kConsentFormStatusAvailable; + debug_geo_ = params.debug_settings.debug_geography; + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::LoadConsentForm() { + SafeFutureHandle handle = CreateFuture(kConsentInfoFnLoadConsentForm); + + if (consent_form_status_ != kConsentFormStatusAvailable) { + CompleteFuture(handle, kConsentFormErrorUnavailable); + return MakeFuture(futures(), handle); + } + CompleteFuture(handle, kConsentFormSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::ShowConsentForm(FormParent parent) { + SafeFutureHandle handle = CreateFuture(kConsentInfoFnShowConsentForm); + + consent_status_ = kConsentStatusObtained; + + if (debug_geo_ == kConsentDebugGeographyEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusRequired; + } else if (debug_geo_ == kConsentDebugGeographyNonEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } else { // no debug option + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +Future ConsentInfoInternalStub::LoadAndShowConsentFormIfRequired( + FormParent parent) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnLoadAndShowConsentFormIfRequired); + + if (consent_status_ == kConsentStatusRequired && + consent_form_status_ != kConsentFormStatusAvailable) { + CompleteFuture(handle, kConsentFormErrorUnavailable); + return MakeFuture(futures(), handle); + } + + if (consent_status_ == kConsentStatusRequired) { + consent_status_ = kConsentStatusObtained; + if (debug_geo_ == kConsentDebugGeographyEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusRequired; + } else if (debug_geo_ == kConsentDebugGeographyNonEEA) { + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } else { // no debug option + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + } + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +PrivacyOptionsRequirementStatus +ConsentInfoInternalStub::GetPrivacyOptionsRequirementStatus() { + return privacy_options_requirement_status_; +} + +Future ConsentInfoInternalStub::ShowPrivacyOptionsForm( + FormParent parent) { + SafeFutureHandle handle = + CreateFuture(kConsentInfoFnShowPrivacyOptionsForm); + + if (consent_status_ == kConsentStatusObtained) { + consent_status_ = kConsentStatusRequired; + privacy_options_requirement_status_ = + kPrivacyOptionsRequirementStatusNotRequired; + } + CompleteFuture(handle, kConsentRequestSuccess); + return MakeFuture(futures(), handle); +} + +bool ConsentInfoInternalStub::CanRequestAds() { + bool consent_status_ok = (consent_status_ == kConsentStatusObtained || + consent_status_ == kConsentStatusNotRequired); + bool privacy_options_ok = (privacy_options_requirement_status_ != + kPrivacyOptionsRequirementStatusUnknown); + return consent_status_ok && privacy_options_ok; +} + +void ConsentInfoInternalStub::Reset() { + consent_status_ = kConsentStatusUnknown; + consent_form_status_ = kConsentFormStatusUnknown; +} + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase diff --git a/ump/src/stub/ump/consent_info_internal_stub.h b/ump/src/stub/ump/consent_info_internal_stub.h new file mode 100644 index 0000000000..83368de284 --- /dev/null +++ b/ump/src/stub/ump/consent_info_internal_stub.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ +#define FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ + +#include "gma/src/common/ump/consent_info_internal.h" + +namespace firebase { +namespace gma { +namespace ump { +namespace internal { + +// The stub interface implements a few specific workflows, for testing: +// +// Before requesting: consent and privacy options requirement will be Unknown. +// +// After requesting: +// +// If debug_geography == EEA, consent will be Required, privacy options +// NotRequired. After calling ShowConsentForm() or +// LoadAndShowConsentFormIfRequired(), it will change to change to Obtained and +// privacy options will become Required, and when the privacy options form is +// shown, consent will go back to Required. +// +// If debug_geography == NonEEA, consent will be NotRequired. No privacy options +// form is required. +// +// If debug_geography == Disabled, consent will be Obtained and privacy options +// will be NotRequired. +// +// If tag_for_under_age_of_consent = true, LoadConsentForm and +// LoadAndShowConsentFormIfRequired will fail with kConsentFormErrorUnavailable. +// +// CanRequestAds returns true if consent is NotRequired or Obtained. +class ConsentInfoInternalStub : public ConsentInfoInternal { + public: + ConsentInfoInternalStub(); + ~ConsentInfoInternalStub() override; + + ConsentStatus GetConsentStatus() override { return consent_status_; } + ConsentFormStatus GetConsentFormStatus() override { + return consent_form_status_; + } + + Future RequestConsentInfoUpdate( + const ConsentRequestParameters& params) override; + Future LoadConsentForm() override; + Future ShowConsentForm(FormParent parent) override; + + Future LoadAndShowConsentFormIfRequired(FormParent parent) override; + + PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus() override; + Future ShowPrivacyOptionsForm(FormParent parent) override; + + bool CanRequestAds() override; + + void Reset() override; + + private: + ConsentStatus consent_status_; + ConsentFormStatus consent_form_status_; + PrivacyOptionsRequirementStatus privacy_options_requirement_status_; + ConsentDebugGeography debug_geo_; + bool under_age_of_consent_; +}; + +} // namespace internal +} // namespace ump +} // namespace gma +} // namespace firebase + +#endif // FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java new file mode 100644 index 0000000000..9d7e1add66 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 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.gma.internal.cpp; + +import android.util.Log; +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.AdInspectorError; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.OnAdInspectorClosedListener; + +/** Helper class for listening to AdInspector closed events. */ +public final class AdInspectorHelper implements OnAdInspectorClosedListener { + /** + * Pointer to the C++ AdInspectorClosedListener object to invoke when the AdInspector has been + * closed. + */ + private long mNativeCallbackPtr; + + /** Constructor. */ + AdInspectorHelper(long nativeCallbackPtr) { + mNativeCallbackPtr = nativeCallbackPtr; + } + + /** Method that the Android GMA SDK invokes when the AdInspector has been closed. */ + @Override + public void onAdInspectorClosed(AdInspectorError error) { + adInspectorClosedCallback(mNativeCallbackPtr, error); + } + + /** + * Native callback to which will signal the customer's application that the AdInspector has been + * closed. A null AdError signifies success. + */ + public static native void adInspectorClosedCallback(long nativeCallbackPtr, AdError error); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java new file mode 100644 index 0000000000..a06d0a4674 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +import java.util.Calendar; +import java.util.Date; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java AdRequest objects cleaner. + * This involves translating calls coming from C++ into their (typically more complicated) Java + * equivalents. + */ +public class AdRequestHelper { + public AdRequestHelper() {} + + /** + * Creates a {@link java.lang.Date} from the provided date information. + * + * @param year The year to use in creating the Date object + * @param month The month to use in creating the Date object + * @param day The day to use in creating the Date object + * @return A Date object with the appropriate date + */ + public Date createDate(int year, int month, int day) { + try { + Calendar cal = Calendar.getInstance(); + cal.setLenient(false); + cal.set(year, month - 1, day); + return cal.getTime(); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java new file mode 100644 index 0000000000..5c91977228 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java @@ -0,0 +1,658 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +import android.app.Activity; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewTreeObserver; +import android.widget.PopupWindow; +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.AdValue; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.OnPaidEventListener; +import com.google.android.gms.ads.ResponseInfo; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java AdView objects cleaner. + * It's designed to wrap and adapt a single instance of AdView, translate calls coming from C++ into + * their (typically more complicated) Java equivalents, and convert the Java listener patterns into + * game engine-friendly state machine polling. + */ +public class AdViewHelper implements ViewTreeObserver.OnPreDrawListener { + // It's possible to attempt to show a popup when an activity doesn't have focus. This value + // controls the number of times the AdViewHelper object checks for activity window focus + // before timing out. Assuming 10ms per retry this value attempts to retry for 2 minutes before + // timing out. + private static final int POPUP_SHOW_RETRY_COUNT = 12000; + + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // The number of milliseconds to wait before attempting to create a PopUpWindow to hold an ad. + private static final int WEBVIEW_DELAY_MILLISECONDS = 200; + + // Pointer to the AdViewInternalAndroid object that created this object. + private long mAdViewInternalPtr; + + // The GMA SDK AdView associated with this helper. + private AdView mAdView; + + // Flag indicating whether an ad is showing in mAdView. + private boolean mAdViewContainsAd; + + // Flag indicating that the Bounding Box listener callback should be invoked + // the next time mAdView's OnPreDrawListener gets an OnPreDraw event. + private AtomicBoolean mNotifyBoundingBoxListenerOnNextDraw; + + // The {@link Activity} this helper uses to display its {@link AdView}. + private Activity mActivity; + + // The ad unit ID to use for the {@link AdView}. + private String mAdUnitId; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to LoadAd. + private long mLoadAdCallbackDataPtr; + + // Synchronization object for thread safe access to: + // * mAdViewInternalPtr + // * mLoadAdCallbackDataPtr + private final Object mAdViewLock; + + // {@link PopupWindow } that will contain the {@link AdView}. This is done to + // guarantee the ad is drawn properly even when the application uses a + // {@link android.app.NativeActivity}. + private PopupWindow mPopUp; + + // Runnable that is trigged to show the Ad {@link PopupWindow}. + // When this is set to null, the popup should not be shown. + private Runnable mPopUpRunnable; + + // Lock used to synchronize the state of the popup window. + private final Object mPopUpLock; + + // Number of times the AdViewHelper object has attempted to show the popup window before the + // activity has focus. + private int mPopUpShowRetryCount; + + // Flag indicating whether the {@link AdView} is currently intended to be + // positioned with (x,y) coordinates rather than one of the pre-defined + // positions (such as ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT). + private boolean mShouldUseXYForPosition; + + // The user's desired pre-defined position for the {@link AdView}. + private int mDesiredPosition; + + // The user's desired pre-defined X coordinate for the {@link AdView}. + private int mDesiredX; + + // The user's desired pre-defined Y coordinate for the {@link AdView}. + private int mDesiredY; + + /** + * Constructor. + */ + public AdViewHelper(long AdViewInternalPtr, AdView adView) { + mAdViewInternalPtr = AdViewInternalPtr; + mAdView = adView; + mDesiredPosition = ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT; + mShouldUseXYForPosition = false; + mAdViewContainsAd = false; + mNotifyBoundingBoxListenerOnNextDraw = new AtomicBoolean(false); + mAdViewLock = new Object(); + mPopUpLock = new Object(); + mPopUpShowRetryCount = 0; + } + + /** + * Initializes the {@link AdView}. This stores the activity for use with + * callback and load operations. + */ + + public void initialize(Activity activity) { + mActivity = activity; + } + + /** + * Destroy/deallocate the {@link PopupWindow} and {@link AdView}. + */ + public void destroy(final long callbackDataPtr, final boolean destructor_invocation) { + // If the Activity isn't initialized, or already Destroyed, then there's + // nothing to destroy. + if (mActivity != null) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + // Stop any attempts to show the popup window. + synchronized (mPopUpLock) { + mPopUpRunnable = null; + } + + if (mAdView != null) { + mAdView.setAdListener(null); + mAdView.setOnPaidEventListener(null); + mAdView.destroy(); + mAdView = null; + } + + synchronized (mPopUpLock) { + if (mPopUp != null) { + mPopUp.dismiss(); + mPopUp = null; + } + } + synchronized (mAdViewLock) { + if (destructor_invocation == false) { + notifyBoundingBoxChanged(mAdViewInternalPtr); + } + mAdViewInternalPtr = CPP_NULLPTR; + } + mActivity = null; + if (destructor_invocation) { + // AdViews's C++ destructor does not pass a future + // to callback and complete, but the reference to this object + // which should be released. + releaseAdViewGlobalReferenceCallback(callbackDataPtr); + } else { + completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + } + }); + } else { + if (callbackDataPtr != CPP_NULLPTR) { + completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + } + } + + /** + * Loads an ad for the underlying AdView object. + */ + public void loadAd(long callbackDataPtr, final AdRequest request) { + if (mActivity == null) { + return; + } + + synchronized (mAdViewLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeAdViewLoadAdInternalError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + mLoadAdCallbackDataPtr = callbackDataPtr; + } + + if (mAdView == null) { + synchronized (mAdViewLock) { + completeAdViewLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } else { + mAdView.loadAd(request); + } + } + + /** + * Hides the {@link AdView}. + */ + public void hide(final long callbackDataPtr) { + if (mActivity == null) { + return; + } + + int errorCode; + String errorMessage; + + synchronized (mPopUpLock) { + // Stop any attempts to show the popup window. + mPopUpRunnable = null; + + if (mAdView == null || mPopUp == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mPopUp.dismiss(); + mPopUp = null; + } + } + + completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + + /** + * Shows the {@link AdView}. + */ + public void show(final long callbackDataPtr) { + if (mActivity == null) { + return; + } + updatePopUpLocation(callbackDataPtr); + } + + /** + * Pauses the {@link AdView}. + */ + public void pause(final long callbackDataPtr) { + if (mActivity == null) { + return; + } else if (mAdView != null) { + mAdView.pause(); + } + completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Resume the {@link AdView} (from a pause). + */ + public void resume(final long callbackDataPtr) { + if (mActivity == null) { + return; + } else if (mAdView != null) { + mAdView.resume(); + } + + completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Moves the {@link AdView} to the provided (x,y) screen coordinates. + */ + public void moveTo(final long callbackDataPtr, int x, int y) { + if (mActivity == null) { + return; + } + + synchronized (mPopUpLock) { + mShouldUseXYForPosition = true; + mDesiredX = x; + mDesiredY = y; + if (mPopUp != null) { + updatePopUpLocation(callbackDataPtr); + } + } + } + + /** + * Moves the {@link AdView} to the provided screen position. + */ + public void moveTo(final long callbackDataPtr, final int position) { + if (mActivity == null) { + return; + } + + synchronized (mPopUpLock) { + mShouldUseXYForPosition = false; + mDesiredPosition = position; + if (mPopUp != null) { + updatePopUpLocation(callbackDataPtr); + } + } + } + + /** + * Returns an integer array consisting of the current onscreen width, height, x-coordinate, and + * y-coordinate of the {@link AdView}. These values make up the AdView's BoundingBox. + */ + public int[] getBoundingBox() { + synchronized (mPopUpLock) { + int width = -1; + int height = -1; + int x = -1; + int y = -1; + if (mPopUp != null) { + int[] onScreenLocation = new int[2]; + mPopUp.getContentView().getLocationOnScreen(onScreenLocation); + x = onScreenLocation[0]; + y = onScreenLocation[1]; + + if (mAdView != null) { + if (mPopUp.isShowing()) { + width = mAdView.getWidth(); + height = mAdView.getHeight(); + } else { + width = height = 0; + } + } + } + return new int[] {width, height, x, y}; + } + } + + /** + * Returns an integer representation of the AdView's position. + */ + public int getPosition() { + if (mAdView == null || mShouldUseXYForPosition) { + return ConstantsHelper.AD_VIEW_POSITION_UNDEFINED; + } + return mDesiredPosition; + } + + /** + * Displays the {@link PopupWindow} that contains the {@link AdView}, in accordance with the + * parameters of the last call to MoveTo. + * + *

This method must be called on the UI Thread. + * + * @return true if successful, false otherwise. + */ + private boolean updatePopUpLocation(final long callbackDataPtr) { + if (mActivity == null) { + return false; + } + final View view = mActivity.findViewById(android.R.id.content); + if (view == null) { + return false; + } + + // If mActivity's content view doesn't have a window token, it will be + // impossible to update or display the popup later in this method. This is + // a rare case caused by mActivity spinning up or winding down, but it will + // cause the WindowManager to crash. + final View root = view.getRootView(); + if (root == null || root.getWindowToken() == null) { + return false; + } + + synchronized (mPopUpLock) { + if (mPopUp != null) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mPopUpLock) { + // Any change in visibility or position results in the dismissal of the popup (if + // one is being displayed) and creation of a fresh one. + mPopUp.dismiss(); + mPopUp = null; + } + } + }); + } + + mPopUpShowRetryCount = 0; + mPopUpRunnable = new Runnable() { + @Override + public void run() { + int errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + String errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + // If the Activity's window doesn't currently have focus it's not + // possible to display the popup window. Poll the focus after a delay of 10ms and try + // to show the popup again. + if (!mActivity.hasWindowFocus()) { + synchronized (mPopUpLock) { + if (mPopUpRunnable == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else { + if (mPopUpShowRetryCount < POPUP_SHOW_RETRY_COUNT) { + mPopUpShowRetryCount++; + new Handler().postDelayed(mPopUpRunnable, 10); + return; + } + errorCode = ConstantsHelper.CALLBACK_ERROR_NO_WINDOW_TOKEN; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NO_WINDOW_TOKEN; + } + } + } + + if (mAdView == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } + + if (errorCode != ConstantsHelper.CALLBACK_ERROR_NONE) { + completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); + return; + } else if (mPopUp == null) { + mPopUp = new PopupWindow(mAdView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + mPopUp.setBackgroundDrawable(new ColorDrawable(0xFF000000)); // Black + mAdView.getViewTreeObserver().addOnPreDrawListener(AdViewHelper.this); + + if (mShouldUseXYForPosition) { + mPopUp.showAtLocation(root, Gravity.NO_GRAVITY, mDesiredX, mDesiredY); + } else { + switch (mDesiredPosition) { + case ConstantsHelper.AD_VIEW_POSITION_TOP: + mPopUp.showAtLocation(root, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0); + break; + case ConstantsHelper.AD_VIEW_POSITION_BOTTOM: + mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0); + break; + case ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT: + mPopUp.showAtLocation(root, Gravity.TOP | Gravity.LEFT, 0, 0); + break; + case ConstantsHelper.AD_VIEW_POSITION_TOP_RIGHT: + mPopUp.showAtLocation(root, Gravity.TOP | Gravity.RIGHT, 0, 0); + break; + case ConstantsHelper.AD_VIEW_POSITION_BOTTOM_LEFT: + mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.LEFT, 0, 0); + break; + case ConstantsHelper.AD_VIEW_POSITION_BOTTOM_RIGHT: + mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.RIGHT, 0, 0); + break; + default: + mPopUp.showAtLocation(root, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0); + break; + } + } + } + + completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); + mNotifyBoundingBoxListenerOnNextDraw.set(true); + } + }; + } + + // TODO(b/31391149): This delay is a workaround for b/31391149, and should be removed once + // that bug is resolved. + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.postDelayed(mPopUpRunnable, WEBVIEW_DELAY_MILLISECONDS); + + return true; + } + + public class AdViewListener extends AdListener implements OnPaidEventListener { + @Override + public void onAdClicked() { + synchronized (mAdViewLock) { + if (mAdViewInternalPtr != CPP_NULLPTR) { + notifyAdClicked(mAdViewInternalPtr); + } + } + super.onAdClicked(); + } + + @Override + public void onAdClosed() { + synchronized (mAdViewLock) { + if (mAdViewInternalPtr != CPP_NULLPTR) { + notifyAdClosed(mAdViewInternalPtr); + mNotifyBoundingBoxListenerOnNextDraw.set(true); + } + } + super.onAdClosed(); + } + + @Override + public void onAdFailedToLoad(LoadAdError loadAdError) { + synchronized (mAdViewLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeAdViewLoadAdError( + mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + super.onAdFailedToLoad(loadAdError); + } + + @Override + public void onAdImpression() { + synchronized (mAdViewLock) { + if (mAdViewInternalPtr != CPP_NULLPTR) { + notifyAdImpression(mAdViewInternalPtr); + } + } + super.onAdImpression(); + } + + @Override + public void onAdLoaded() { + synchronized (mAdViewLock) { + if (mAdView != null) { + mAdViewContainsAd = true; + } + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + AdSize adSize = mAdView.getAdSize(); + completeAdViewLoadedAd(mLoadAdCallbackDataPtr, mAdViewInternalPtr, adSize.getWidth(), + adSize.getHeight(), mAdView.getResponseInfo()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + // Only update the bounding box if the banner view is already visible. + if (mPopUp != null && mPopUp.isShowing()) { + // Loading an ad can sometimes cause the bounds to change. + mNotifyBoundingBoxListenerOnNextDraw.set(true); + } + } + super.onAdLoaded(); + } + + @Override + public void onAdOpened() { + synchronized (mAdViewLock) { + if (mAdViewInternalPtr != CPP_NULLPTR) { + notifyAdOpened(mAdViewInternalPtr); + } + mNotifyBoundingBoxListenerOnNextDraw.set(true); + } + super.onAdOpened(); + } + + public void onPaidEvent(AdValue value) { + synchronized (mAdViewLock) { + if (mAdViewInternalPtr != CPP_NULLPTR) { + notifyPaidEvent(mAdViewInternalPtr, value.getCurrencyCode(), value.getPrecisionType(), + value.getValueMicros()); + } + } + } + } + + /** + * Implementation of ViewTreeObserver.OnPreDrawListener's onPreDraw method. This gets called when + * mAdView is about to be redrawn, and checks a flag before invoking the native callback that + * tells the C++ side a Bounding Box change has occurred and the AdView::Listener (if there is + * one) needs to be notified. + * + *

By invoking the listener callback here, hooked into the draw loop, the AdViewHelper + * object can be sure that any movements of mAdView have been completed and the layout and screen + * position have been recalculated by the time the notification happens, preventing stale data + * from getting to the Listener. + */ + @Override + public boolean onPreDraw() { + if (mNotifyBoundingBoxListenerOnNextDraw.compareAndSet(true, false)) { + if (mAdView != null && mAdViewInternalPtr != CPP_NULLPTR) { + notifyBoundingBoxChanged(mAdViewInternalPtr); + } + } + // Returning true tells Android to continue the draw as normal. + return true; + } + + /** + * Native callback to instruct the C++ wrapper to complete the corresponding future. + */ + public static native void completeAdViewFutureCallback( + long nativeInternalPtr, int errorCode, String errorMessage); + + /** + * Native callback to instruct the C++ wrapper to release its global reference on this + * object. + */ + public static native void releaseAdViewGlobalReferenceCallback(long nativeInternalPtr); + + /** + * Native callback invoked upon successfully loading an ad. + */ + public static native void completeAdViewLoadedAd(long nativeInternalPtr, long mAdViewInternalPtr, + int width, int height, ResponseInfo responseInfo); + + /** + * Native callback upon encountering an error loading an Ad Request. Returns + * Android Google Mobile Ads SDK error codes. + **/ + public static native void completeAdViewLoadAdError( + long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); + + /** + * Native callback upon encountering a wrapper/internal error when + * processing a Load Ad Request. Returns an integer representing + * firebase::gma::AdError codes. + */ + public static native void completeAdViewLoadAdInternalError( + long nativeInternalPtr, int gmaErrorCode, String errorMessage); + + /** + * Native callback to notify the C++ wrapper that the Ad's Bounding Box has changed. + */ + public static native void notifyBoundingBoxChanged(long nativeInternalPtr); + + /** + * Native callback to notify the C++ wrapper of an ad clicked event + */ + public static native void notifyAdClicked(long nativeInternalPtr); + + /** + * Native callback to notify the C++ wrapper of an ad closed event + */ + public static native void notifyAdClosed(long nativeInternalPtr); + + /** + * Native callback to notify the C++ wrapper of an ad impression event + */ + public static native void notifyAdImpression(long nativeInternalPtr); + + /** + * Native callback to notify the C++ wrapper of an ad opened event + */ + public static native void notifyAdOpened(long nativeInternalPtr); + + /** + * Native callback to notify the C++ wrapper that a paid event has occurred. + */ + public static native void notifyPaidEvent( + long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java new file mode 100644 index 0000000000..4b2bf1852b --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java @@ -0,0 +1,312 @@ +/* + * 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.gma.internal.cpp; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import com.google.android.ump.ConsentDebugSettings; +import com.google.android.ump.ConsentForm; +import com.google.android.ump.ConsentForm.OnConsentFormDismissedListener; +import com.google.android.ump.ConsentInformation; +import com.google.android.ump.ConsentInformation.OnConsentInfoUpdateFailureListener; +import com.google.android.ump.ConsentInformation.OnConsentInfoUpdateSuccessListener; +import com.google.android.ump.ConsentInformation.PrivacyOptionsRequirementStatus; +import com.google.android.ump.ConsentRequestParameters; +import com.google.android.ump.FormError; +import com.google.android.ump.UserMessagingPlatform; +import com.google.android.ump.UserMessagingPlatform.OnConsentFormLoadFailureListener; +import com.google.android.ump.UserMessagingPlatform.OnConsentFormLoadSuccessListener; +import java.util.ArrayList; + +/** + * Helper class to make interactions between the GMA UMP C++ wrapper and the Android UMP API. + */ +public class ConsentInfoHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Synchronization object for thread safe access to: + private final Object mLock = new Object(); + // Pointer to the internal ConsentInfoInternalAndroid C++ object. + // This can be reset back to 0 by calling disconnect(). + private long mInternalPtr = 0; + // The Activity that this was initialized with. + private Activity mActivity = null; + // The loaded consent form, if any. + private ConsentForm mConsentForm = null; + + // Create our own local passthrough version of these enum object values + // as integers, to make it easier for the C++ SDK to access them. + public static final int PRIVACY_OPTIONS_REQUIREMENT_UNKNOWN = + PrivacyOptionsRequirementStatus.UNKNOWN.ordinal(); + public static final int PRIVACY_OPTIONS_REQUIREMENT_REQUIRED = + PrivacyOptionsRequirementStatus.REQUIRED.ordinal(); + public static final int PRIVACY_OPTIONS_REQUIREMENT_NOT_REQUIRED = + PrivacyOptionsRequirementStatus.NOT_REQUIRED.ordinal(); + + // Enum values for tracking which function we are calling back. + // Ensure these are incremental starting at 0. + // These don't have to match ConsentInfoFn, as the C++ code will + // use these Java enums directly. + public static final int FUNCTION_REQUEST_CONSENT_INFO_UPDATE = 0; + public static final int FUNCTION_LOAD_CONSENT_FORM = 1; + public static final int FUNCTION_SHOW_CONSENT_FORM = 2; + public static final int FUNCTION_LOAD_AND_SHOW_CONSENT_FORM_IF_REQUIRED = 3; + public static final int FUNCTION_SHOW_PRIVACY_OPTIONS_FORM = 4; + public static final int FUNCTION_COUNT = 5; + + public ConsentInfoHelper(long consentInfoInternalPtr, Activity activity) { + synchronized (mLock) { + mInternalPtr = consentInfoInternalPtr; + mActivity = activity; + // Test the callbacks and fail quickly if something's wrong. + completeFuture(-1, CPP_NULLPTR, CPP_NULLPTR, 0, null); + } + } + + public int getConsentStatus() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + return consentInfo.getConsentStatus(); + } + + public void requestConsentInfoUpdate(final long futureHandle, boolean tagForUnderAgeOfConsent, + int debugGeography, ArrayList debugIdList) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + } + final int functionId = FUNCTION_REQUEST_CONSENT_INFO_UPDATE; + + ConsentDebugSettings.Builder debugSettingsBuilder = null; + + // Only create and use debugSettingsBuilder if a debug option is set. + if (debugGeography != ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_DISABLED) { + debugSettingsBuilder = + new ConsentDebugSettings.Builder(mActivity).setDebugGeography(debugGeography); + } + if (debugIdList != null && debugIdList.size() > 0) { + if (debugSettingsBuilder == null) { + debugSettingsBuilder = new ConsentDebugSettings.Builder(mActivity); + } + for (int i = 0; i < debugIdList.size(); i++) { + debugSettingsBuilder = debugSettingsBuilder.addTestDeviceHashedId(debugIdList.get(i)); + } + } + ConsentRequestParameters.Builder paramsBuilder = + new ConsentRequestParameters.Builder().setTagForUnderAgeOfConsent(tagForUnderAgeOfConsent); + + if (debugSettingsBuilder != null) { + paramsBuilder = paramsBuilder.setConsentDebugSettings(debugSettingsBuilder.build()); + } + + final ConsentRequestParameters params = paramsBuilder.build(); + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + consentInfo.requestConsentInfoUpdate(mActivity, params, + new OnConsentInfoUpdateSuccessListener() { + @Override + public void onConsentInfoUpdateSuccess() { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + completeFuture(functionId, mInternalPtr, futureHandle, 0, null); + } + } + }, + new OnConsentInfoUpdateFailureListener() { + @Override + public void onConsentInfoUpdateFailure(FormError formError) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + completeFuture(functionId, mInternalPtr, futureHandle, formError.getErrorCode(), + formError.getMessage()); + } + } + }); + } + }); + } + + public void loadConsentForm(final long futureHandle) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + } + final int functionId = FUNCTION_LOAD_CONSENT_FORM; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + UserMessagingPlatform.loadConsentForm(mActivity, + new OnConsentFormLoadSuccessListener() { + @Override + public void onConsentFormLoadSuccess(ConsentForm form) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + mConsentForm = form; + completeFuture(functionId, mInternalPtr, futureHandle, 0, null); + } + } + }, + new OnConsentFormLoadFailureListener() { + @Override + public void onConsentFormLoadFailure(FormError formError) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + mConsentForm = null; + completeFuture(functionId, mInternalPtr, futureHandle, formError.getErrorCode(), + formError.getMessage()); + } + } + }); + } + }); + } + + public boolean showConsentForm(final long futureHandle, final Activity activity) { + synchronized (mLock) { + if (mInternalPtr == 0) + return false; + } + final int functionId = FUNCTION_SHOW_CONSENT_FORM; + ConsentForm consentForm; + synchronized (mLock) { + if (mConsentForm == null) { + // Consent form was not loaded, return an error. + return false; + } + consentForm = mConsentForm; + mConsentForm = null; + } + final ConsentForm consentFormForThread = consentForm; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + consentFormForThread.show(activity, new OnConsentFormDismissedListener() { + @Override + public void onConsentFormDismissed(FormError formError) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + if (formError == null) { + completeFuture(functionId, mInternalPtr, futureHandle, 0, null); + } else { + completeFuture(functionId, mInternalPtr, futureHandle, formError.getErrorCode(), + formError.getMessage()); + } + } + } + }); + } + }); + // Consent form is loaded. + return true; + } + + public void loadAndShowConsentFormIfRequired(final long futureHandle, final Activity activity) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + } + final int functionId = FUNCTION_LOAD_AND_SHOW_CONSENT_FORM_IF_REQUIRED; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + UserMessagingPlatform.loadAndShowConsentFormIfRequired( + activity, new OnConsentFormDismissedListener() { + @Override + public void onConsentFormDismissed(FormError formError) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + if (formError == null) { + completeFuture(functionId, mInternalPtr, futureHandle, 0, null); + } else { + completeFuture(functionId, mInternalPtr, futureHandle, formError.getErrorCode(), + formError.getMessage()); + } + } + } + }); + } + }); + } + + public int getPrivacyOptionsRequirementStatus() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + return consentInfo.getPrivacyOptionsRequirementStatus().ordinal(); + } + + public void showPrivacyOptionsForm(final long futureHandle, final Activity activity) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + } + final int functionId = FUNCTION_SHOW_PRIVACY_OPTIONS_FORM; + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + UserMessagingPlatform.showPrivacyOptionsForm( + activity, new OnConsentFormDismissedListener() { + @Override + public void onConsentFormDismissed(FormError formError) { + synchronized (mLock) { + if (mInternalPtr == 0) + return; + if (formError == null) { + completeFuture(functionId, mInternalPtr, futureHandle, 0, null); + } else { + completeFuture(functionId, mInternalPtr, futureHandle, formError.getErrorCode(), + formError.getMessage()); + } + } + } + }); + } + }); + } + + public boolean canRequestAds() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + return consentInfo.canRequestAds(); + } + + public boolean isConsentFormAvailable() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + return consentInfo.isConsentFormAvailable(); + } + + public void reset() { + ConsentInformation consentInfo = UserMessagingPlatform.getConsentInformation(mActivity); + consentInfo.reset(); + } + + /** Disconnect the helper from the native object. */ + public void disconnect() { + synchronized (mLock) { + mInternalPtr = CPP_NULLPTR; + } + } + public static native void completeFuture( + int futureFn, long nativeInternalPtr, long futureHandle, int errorCode, String errorMessage); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java new file mode 100644 index 0000000000..71da84ff2b --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +/** Helper class containing constants that are shared across the various GMA ad formats. */ +public final class ConstantsHelper { + /** Error codes used in completing futures. These match the AdError enumeration in the C++ API. */ + public static final int CALLBACK_ERROR_NONE = 0; + + public static final int CALLBACK_ERROR_UNINITIALIZED = 1; + + public static final int CALLBACK_ERROR_ALREADY_INITIALIZED = 2; + + public static final int CALLBACK_ERROR_LOAD_IN_PROGRESS = 3; + + public static final int CALLBACK_ERROR_INTERNAL_ERROR = 4; + + public static final int CALLBACK_ERROR_INVALID_REQUEST = 5; + + public static final int CALLBACK_ERROR_NETWORK_ERROR = 6; + + public static final int CALLBACK_ERROR_NO_FILL = 7; + + public static final int CALLBACK_ERROR_NO_WINDOW_TOKEN = 8; + + public static final int CALLBACK_ERROR_UNKNOWN = 9; + + /** + * Error messages used for completing futures. These match the error codes in the AdError + * enumeration in the C++ API. + */ + public static final String CALLBACK_ERROR_MESSAGE_NONE = ""; + + public static final String CALLBACK_ERROR_MESSAGE_UNINITIALIZED = + "Ad has not been fully initialized."; + + public static final String CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED = + "Ad is already initialized."; + + public static final String CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS = "Ad is currently loading."; + + public static final String CALLBACK_ERROR_MESSAGE_INTERNAL_ERROR = + "An internal SDK error occurred."; + + public static final String CALLBACK_ERROR_MESSAGE_INVALID_REQUEST = "Invalid ad request."; + + public static final String CALLBACK_ERROR_MESSAGE_NETWORK_ERROR = "A network error occurred."; + + public static final String CALLBACK_ERROR_MESSAGE_NO_FILL = "No ad was available to serve."; + + public static final String CALLBACK_ERROR_MESSAGE_NO_WINDOW_TOKEN = + "Android Activity does not have a window token."; + + public static final String CALLBACK_ERROR_MESSAGE_UNKNOWN = "Unknown error occurred."; + + /** + * Ad view positions (matches the AdView::Position and NativeExpressAdView::Position enumerations + * in the public C++ API). + */ + public static final int AD_VIEW_POSITION_UNDEFINED = -1; + + public static final int AD_VIEW_POSITION_TOP = 0; + + public static final int AD_VIEW_POSITION_BOTTOM = 1; + + public static final int AD_VIEW_POSITION_TOP_LEFT = 2; + + public static final int AD_VIEW_POSITION_TOP_RIGHT = 3; + + public static final int AD_VIEW_POSITION_BOTTOM_LEFT = 4; + + public static final int AD_VIEW_POSITION_BOTTOM_RIGHT = 5; + + /** + * Ad formats (matches the firebase::gma::AdFormat and com.google.android.gms.ads.AdFormat + * enumerations ). + */ + public static final int AD_FORMAT_UNDEFINED = -1; + + public static final int AD_FORMAT_APP_OPEN_AD = 0; + + public static final int AD_FORMAT_BANNER = 1; + + public static final int AD_FORMAT_INTERSTITIAL = 2; + + public static final int AD_FORMAT_NATIVE = 3; + + public static final int AD_FORMAT_REWARDED = 4; + + public static final int AD_FORMAT_REWARDED_INTERSTITIAL = 5; +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java new file mode 100644 index 0000000000..8b1b5dc9b1 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java @@ -0,0 +1,198 @@ +/* + * 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.gma.internal.cpp; + +import android.os.AsyncTask; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's + * designed to download static ad assets from network. + */ +public class DownloadHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // URL to download the asset from. + private URL url; + + // Request headers. + private final HashMap headers; + + // HTTP response code. + private int responseCode; + + // Error message. + private String errorMessage; + + // Error code. + private int errorCode; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to LoadImage. + private long mDownloadCallbackDataPtr; + + // Synchronization object for thread safe access to: + // * mDownloadCallbackDataPtr + private final Object mDownloadLock; + + // Create a new DownloadHelper with a default URL. + public DownloadHelper(String urlString) throws MalformedURLException { + this.headers = new HashMap<>(); + setUrl(urlString); + + mDownloadLock = new Object(); + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + + // Test the callbacks and fail quickly if something's wrong. + completeNativeImageFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + // Set the URL to the given string, or null if it can't be parsed. + public void setUrl(String urlString) throws MalformedURLException { + this.url = new URL(urlString); + } + + // Get the previously-set URL. + public URL getUrl() { + return this.url; + } + + // Add a header key-value pair. + public void addHeader(String key, String value) { + this.headers.put(key, value); + } + + // Clear previously-set headers. + public void clearHeaders() { + this.headers.clear(); + } + + // Get the response code returned by the server, after download() is finished. + public int getResponseCode() { + return this.responseCode; + } + + /** Triggers an async HTTP GET request to the given URL, with the given headers. */ + public void download(long callbackDataPtr) { + synchronized (mDownloadLock) { + if (mDownloadCallbackDataPtr != CPP_NULLPTR) { + completeNativeLoadImageError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + mDownloadCallbackDataPtr = callbackDataPtr; + } + + try { + /** Invokes download as an async background task. */ + new DownloadFilesTask().execute(); + } catch (Exception ex) { + completeNativeLoadImageError( + callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_INTERNAL_ERROR, ex.getMessage()); + } + + return; + } + + /** Performs Download task in a background worker thread. */ + private class DownloadFilesTask extends AsyncTask { + protected byte[] doInBackground(Void... params) { + HttpURLConnection connection = null; + ByteArrayOutputStream bytestream = null; + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + for (Map.Entry entry : headers.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + + responseCode = connection.getResponseCode(); + + if (responseCode != HttpURLConnection.HTTP_OK) { + errorCode = ConstantsHelper.CALLBACK_ERROR_NETWORK_ERROR; + errorMessage = connection.getResponseMessage(); + + connection.disconnect(); + return null; + } + + InputStream inputStream = connection.getInputStream(); + bytestream = new ByteArrayOutputStream(); + int ch = 0; + + // Buffer to read 1024 bytes in at a time. + byte[] buffer = new byte[1024]; + while ((ch = inputStream.read(buffer)) != -1) { + bytestream.write(buffer, 0, ch); + } + byte imgdata[] = bytestream.toByteArray(); + + bytestream.close(); + connection.disconnect(); + + return imgdata; + } catch (Exception ex) { + errorCode = ConstantsHelper.CALLBACK_ERROR_INVALID_REQUEST; + errorMessage = ex.getMessage(); + } + + try { + if (bytestream != null) { + bytestream.close(); + } + if (connection != null) { + connection.disconnect(); + } + } catch (Exception ex) { + // Connection and bytestream close exceptions can be ignored as download errors are already + // recorded. + } + return null; + } + + protected void onPostExecute(byte[] result) { + synchronized (mDownloadLock) { + if (mDownloadCallbackDataPtr != CPP_NULLPTR) { + if (errorCode != ConstantsHelper.CALLBACK_ERROR_NONE) { + completeNativeLoadImageError(mDownloadCallbackDataPtr, errorCode, errorMessage); + } else { + completeNativeLoadedImage(mDownloadCallbackDataPtr, result); + } + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeNativeImageFutureCallback( + long nativeImagePtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully downloading an image. */ + public static native void completeNativeLoadedImage(long nativeImagePtr, byte[] image); + + /** Native callback upon encountering an error downloading an image. */ + public static native void completeNativeLoadImageError( + long nativeImagePtr, int errorCode, String errorMessage); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java new file mode 100644 index 0000000000..85aee3c383 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +import android.content.Context; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.initialization.InitializationStatus; +import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; + +/** Helper class for initializing the Google Mobile Ads SDK. */ +public final class GmaInitializationHelper { + public static void initializeGma(Context context) { + MobileAds.initialize(context, new OnInitializationCompleteListener() { + @Override + public void onInitializationComplete(InitializationStatus initializationStatus) { + initializationCompleteCallback(initializationStatus); + } + }); + } + + public static native void initializationCompleteCallback( + InitializationStatus initializationStatus); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java new file mode 100644 index 0000000000..e08d82345a --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java @@ -0,0 +1,319 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +import android.app.Activity; +import android.util.Log; +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdValue; +import com.google.android.gms.ads.FullScreenContentCallback; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.OnPaidEventListener; +import com.google.android.gms.ads.ResponseInfo; +import com.google.android.gms.ads.interstitial.InterstitialAd; +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java {@link InterstitialAd} + * objects cleaner. It's designed to wrap and adapt a single instance of {@link InterstitialAd}, + * translate calls coming from C++ into their (typically more complicated) Java equivalents. + */ +public class InterstitialAdHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Pointer to the InterstitialAdInternalAndroid object that created this + // object. + private long mInterstitialAdInternalPtr; + + // The GMA SDK {@link InterstitialAd} associated with this helper. + private InterstitialAd mInterstitial; + + // Synchronization object for thread safe access to: + // * mInterstitial + // * mInterstitialAdInternalPtr + // * mLoadAdCallbackDataPtr + private final Object mInterstitialLock; + + // The {@link Activity} this helper uses to display its + // {@link InterstitialAd}. + private Activity mActivity; + + // The ad unit ID to use for the {@link InterstitialAd}. + private String mAdUnitId; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to LoadAd. + private long mLoadAdCallbackDataPtr; + + /** + * Constructor. + */ + public InterstitialAdHelper(long interstitialAdInternalPtr) { + mInterstitialAdInternalPtr = interstitialAdInternalPtr; + mInterstitialLock = new Object(); + + // Test the callbacks and fail quickly if something's wrong. + completeInterstitialAdFutureCallback( + CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Initializes the {@link InterstitialAd}. This creates the corresponding GMA SDK {@link + * InterstitialAd} object and sets it up. + */ + public void initialize(final long callbackDataPtr, Activity activity) { + mActivity = activity; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + int errorCode; + String errorMessage; + if (mInterstitial == null) { + try { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + } catch (IllegalStateException e) { + mInterstitial = null; + // This exception can be thrown if the ad unit ID was already set. + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + completeInterstitialAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + }); + } + + /** Disconnect the helper from the interstital ad. */ + public void disconnect() { + synchronized (mInterstitialLock) { + mInterstitialAdInternalPtr = CPP_NULLPTR; + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mInterstitialLock) { + if (mInterstitial != null) { + mInterstitial.setFullScreenContentCallback(null); + mInterstitial.setOnPaidEventListener(null); + mInterstitial = null; + } + } + } + }); + } + + /** Loads an ad for the underlying {@link InterstitialAd} object. */ + public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { + if (mActivity == null) { + return; + } + synchronized (mInterstitialLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeInterstitialLoadAdInternalError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + mLoadAdCallbackDataPtr = callbackDataPtr; + } + + mAdUnitId = adUnitId; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (mActivity == null) { + synchronized (mInterstitialLock) { + completeInterstitialLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } else { + try { + InterstitialAd.load(mActivity, mAdUnitId, request, new InterstitialAdListener()); + } catch (IllegalStateException e) { + synchronized (mInterstitialLock) { + completeInterstitialLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + }); + } + + /** Shows a previously loaded ad. */ + public void show(final long callbackDataPtr) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mInterstitialLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mInterstitial == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mInterstitial.show(mActivity); + } + completeInterstitialAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + private class InterstitialAdFullScreenContentListener + extends FullScreenContentCallback implements OnPaidEventListener { + @Override + public void onAdClicked() { + synchronized (mInterstitialLock) { + if (mInterstitialAdInternalPtr != CPP_NULLPTR) { + notifyAdClickedFullScreenContentEvent(mInterstitialAdInternalPtr); + } + } + } + + @Override + public void onAdDismissedFullScreenContent() { + synchronized (mInterstitialLock) { + if (mInterstitialAdInternalPtr != CPP_NULLPTR) { + notifyAdDismissedFullScreenContentEvent(mInterstitialAdInternalPtr); + } + } + } + + @Override + public void onAdFailedToShowFullScreenContent(AdError error) { + synchronized (mInterstitialLock) { + if (mInterstitialAdInternalPtr != CPP_NULLPTR) { + notifyAdFailedToShowFullScreenContentEvent(mInterstitialAdInternalPtr, error); + } + } + } + + @Override + public void onAdImpression() { + synchronized (mInterstitialLock) { + if (mInterstitialAdInternalPtr != CPP_NULLPTR) { + notifyAdImpressionEvent(mInterstitialAdInternalPtr); + } + } + } + + @Override + public void onAdShowedFullScreenContent() { + synchronized (mInterstitialLock) { + if (mInterstitialAdInternalPtr != CPP_NULLPTR) { + notifyAdShowedFullScreenContentEvent(mInterstitialAdInternalPtr); + } + } + } + + public void onPaidEvent(AdValue value) { + synchronized (mInterstitialLock) { + notifyPaidEvent(mInterstitialAdInternalPtr, value.getCurrencyCode(), + value.getPrecisionType(), value.getValueMicros()); + } + } + } + + private class InterstitialAdListener extends InterstitialAdLoadCallback { + @Override + public void onAdFailedToLoad(LoadAdError loadAdError) { + synchronized (mInterstitialLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeInterstitialLoadAdError( + mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + + @Override + public void onAdLoaded(InterstitialAd ad) { + synchronized (mInterstitialLock) { + mInterstitial = ad; + InterstitialAdFullScreenContentListener listener = + new InterstitialAdFullScreenContentListener(); + mInterstitial.setFullScreenContentCallback(listener); + mInterstitial.setOnPaidEventListener(listener); + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeInterstitialLoadedAd(mLoadAdCallbackDataPtr, ad.getResponseInfo()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeInterstitialAdFutureCallback( + long nativeInternalPtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully loading an ad. */ + public static native void completeInterstitialLoadedAd( + long nativeInternalPtr, ResponseInfo responseInfo); + + /** + * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile + * Ads SDK error codes. + */ + public static native void completeInterstitialLoadAdError( + long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); + + /** + * Native callback upon encountering a wrapper/internal error when processing an Ad Request. + * Returns integers representing firebase::gma::AdError codes. + */ + public static native void completeInterstitialLoadAdInternalError( + long nativeInternalPtr, int gmaErrorCode, String errorMessage); + + /** Native callbacks to notify the C++ wrapper of ad events */ + public static native void notifyAdClickedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyAdDismissedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyAdFailedToShowFullScreenContentEvent( + long nativeInternalPtr, AdError adError); + + public static native void notifyAdImpressionEvent(long nativeInternalPtr); + + public static native void notifyAdShowedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyPaidEvent( + long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java new file mode 100644 index 0000000000..a2829e8bcb --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java @@ -0,0 +1,338 @@ +/* + * 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.gma.internal.cpp; + +import android.app.Activity; +import android.os.Bundle; +import com.google.android.gms.ads.AdListener; +import com.google.android.gms.ads.AdLoader; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.ResponseInfo; +import com.google.android.gms.ads.nativead.NativeAd; +import java.util.List; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's + * designed to wrap and adapt a single instance of NativeAd, translate calls coming from C++ into + * their (typically more complicated) Java equivalents. + */ +public class NativeAdHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Pointer to the NativeAdInternalAndroid object that created this + // object. + private long mNativeAdInternalPtr; + + // The GMA SDK NativeAd associated with this helper. + private NativeAd mNative; + + // Synchronization object for thread safe access to: + // * mNative + // * mNativeAdInternalPtr + // * mLoadAdCallbackDataPtr + private final Object mNativeLock; + + // The Activity this helper uses to display its NativeAd. + private Activity mActivity; + + // The ad unit ID to use for the NativeAd. + private String mAdUnitId; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to LoadAd. + private long mLoadAdCallbackDataPtr; + + /** Constructor. */ + public NativeAdHelper(long nativeAdInternalPtr) { + mNativeAdInternalPtr = nativeAdInternalPtr; + mNativeLock = new Object(); + + // Test the callbacks and fail quickly if something's wrong. + completeNativeAdFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Initializes the NativeAd. This creates the corresponding GMA SDK NativeAd object and sets it + * up. + */ + public void initialize(final long callbackDataPtr, Activity activity) { + mActivity = activity; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + int errorCode; + String errorMessage; + if (mNative == null) { + try { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + } catch (IllegalStateException e) { + mNative = null; + // This exception can be thrown if the ad unit ID was already set. + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + }); + } + + /** Disconnect the helper from the native ad. */ + public void disconnect() { + synchronized (mNativeLock) { + mNativeAdInternalPtr = CPP_NULLPTR; + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mNativeLock) { + if (mNative != null) { + mNative = null; + } + } + } + }); + } + + /** Record Impression for allowlisted ad units. */ + public void recordImpression(final long callbackDataPtr, final Bundle payload) { + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mNativeLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mNative == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mNative.recordImpression(payload); + } + completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + /** Perform click for allowlisted ad units. */ + public void performClick(final long callbackDataPtr, final Bundle payload) { + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mNativeLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mNative == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + mNative.performClick(payload); + } + completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + /** Loads an ad for the underlying NativeAd object. */ + public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { + if (mActivity == null) { + return; + } + synchronized (mNativeLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeNativeLoadAdInternalError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + mLoadAdCallbackDataPtr = callbackDataPtr; + } + + mAdUnitId = adUnitId; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (mActivity == null) { + synchronized (mNativeLock) { + completeNativeLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } else { + try { + AdLoader.Builder adLoaderBuilder = new AdLoader.Builder(mActivity, mAdUnitId); + NativeAdListener listener = new NativeAdListener(); + adLoaderBuilder.forNativeAd(listener); + adLoaderBuilder.withAdListener(listener); + AdLoader adLoader = adLoaderBuilder.build(); + adLoader.loadAd(request); + } catch (IllegalStateException e) { + synchronized (mNativeLock) { + completeNativeLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + }); + } + + private class NativeAdListener extends AdListener implements NativeAd.OnNativeAdLoadedListener { + @Override + public void onAdFailedToLoad(LoadAdError loadAdError) { + synchronized (mNativeLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeNativeLoadAdError( + mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + + public void onNativeAdLoaded(NativeAd ad) { + synchronized (mNativeLock) { + mNative = ad; + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + List imgList = ad.getImages(); + NativeAd.Image[] imgArray = new NativeAd.Image[imgList.size()]; + imgArray = imgList.toArray(imgArray); + + NativeAd.Image adChoicesIcon = null; + NativeAd.AdChoicesInfo adChoicesInfo = ad.getAdChoicesInfo(); + if (adChoicesInfo != null) { + List adChoicesImgList = adChoicesInfo.getImages(); + if (!adChoicesImgList.isEmpty()) { + // Gets only the first image to keep the api in sync with its ios counterpart. + adChoicesIcon = adChoicesImgList.get(0); + } + } + + completeNativeLoadedAd(mLoadAdCallbackDataPtr, mNativeAdInternalPtr, ad.getIcon(), + imgArray, adChoicesIcon, ad.getResponseInfo()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + + @Override + public void onAdClicked() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdClicked(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdImpression() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdImpression(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdClosed() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdClosed(mNativeAdInternalPtr); + } + } + } + + @Override + public void onAdOpened() { + synchronized (mNativeLock) { + if (mNativeAdInternalPtr != CPP_NULLPTR) { + notifyAdOpened(mNativeAdInternalPtr); + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeNativeAdFutureCallback( + long nativeInternalPtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully loading an ad. */ + public static native void completeNativeLoadedAd(long nativeInternalPtr, + long mNativeAdInternalPtr, NativeAd.Image icon, NativeAd.Image[] images, + NativeAd.Image adChoicesIcon, ResponseInfo responseInfo); + + /** + * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile + * Ads SDK error codes. + */ + public static native void completeNativeLoadAdError( + long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); + + /** + * Native callback upon encountering a wrapper/internal error when processing an Ad Request. + * Returns integers representing firebase::gma::AdError codes. + */ + public static native void completeNativeLoadAdInternalError( + long nativeInternalPtr, int gmaErrorCode, String errorMessage); + + /** Native callback to notify the C++ wrapper of an ad clicked event */ + public static native void notifyAdClicked(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad closed event */ + public static native void notifyAdClosed(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad impression event */ + public static native void notifyAdImpression(long nativeInternalPtr); + + /** Native callback to notify the C++ wrapper of an ad opened event */ + public static native void notifyAdOpened(long nativeInternalPtr); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java new file mode 100644 index 0000000000..644e905d05 --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java @@ -0,0 +1,218 @@ +/* + * Copyright 2024 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.gma.internal.cpp; + +import android.app.Activity; +import com.google.android.gms.ads.AdFormat; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.query.QueryInfo; +import com.google.android.gms.ads.query.QueryInfoGenerationCallback; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's + * designed to wrap and adapt a single instance of QueryInfo, translate calls coming from C++ into + * their (typically more complicated) Java equivalents. + */ +public class QueryInfoHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Pointer to the QueryInfoInternalAndroid object that created this + // object. + private long queryInfoInternalPtr; + + // The GMA SDK QueryInfo associated with this helper. + private QueryInfo gmaQueryInfo; + + // Synchronization object for thread safe access to: + // * queryInfoInternalPtr + // * createQueryInfoCallbackDataPtr + private final Object queryInfoLock; + + // The Activity this helper uses to generate the QueryInfo. + private Activity activity; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to CreateQueryInfo. + private long createQueryInfoCallbackDataPtr; + + /** Constructor. */ + public QueryInfoHelper(long queryInfoInternalPtr) { + this.queryInfoInternalPtr = queryInfoInternalPtr; + queryInfoLock = new Object(); + + // Test the callbacks and fail quickly if something's wrong. + completeQueryInfoFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Initializes the QueryInfoHelper. This creates the corresponding GMA SDK NativeAd object and + * sets it up. + */ + public void initialize(final long callbackDataPtr, Activity activity) { + this.activity = activity; + + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + int errorCode; + String errorMessage; + if (gmaQueryInfo == null) { + try { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + } catch (IllegalStateException e) { + gmaQueryInfo = null; + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + completeQueryInfoFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + }); + } + + /** Disconnect the helper from the query info. */ + public void disconnect() { + synchronized (queryInfoLock) { + queryInfoInternalPtr = CPP_NULLPTR; + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + + if (activity == null) { + return; + } + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (queryInfoLock) { + if (gmaQueryInfo != null) { + gmaQueryInfo = null; + } + } + } + }); + } + + private AdFormat getAdFormat(int format) { + switch (format) { + case ConstantsHelper.AD_FORMAT_BANNER: + return AdFormat.BANNER; + case ConstantsHelper.AD_FORMAT_INTERSTITIAL: + return AdFormat.INTERSTITIAL; + case ConstantsHelper.AD_FORMAT_REWARDED: + return AdFormat.REWARDED; + case ConstantsHelper.AD_FORMAT_NATIVE: + return AdFormat.NATIVE; + case ConstantsHelper.AD_FORMAT_REWARDED_INTERSTITIAL: + return AdFormat.REWARDED_INTERSTITIAL; + default: + return AdFormat.APP_OPEN_AD; + } + } + + /** Creates a query info for the underlying QueryInfo object. */ + public void createQueryInfo( + long callbackDataPtr, int format, String adUnitId, final AdRequest request) { + if (activity == null) { + return; + } + synchronized (queryInfoLock) { + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + createQueryInfoCallbackDataPtr = callbackDataPtr; + } + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (activity == null) { + synchronized (queryInfoLock) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } else { + try { + AdFormat adFormat = getAdFormat(format); + if (adUnitId != null && !adUnitId.isEmpty()) { + QueryInfo.generate(activity, adFormat, request, adUnitId, new QueryInfoListener()); + } else { + QueryInfo.generate(activity, adFormat, request, new QueryInfoListener()); + } + } catch (IllegalStateException e) { + synchronized (queryInfoLock) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + }); + } + + private class QueryInfoListener extends QueryInfoGenerationCallback { + @Override + public void onFailure(String errorMessage) { + synchronized (queryInfoLock) { + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_INVALID_REQUEST, errorMessage); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + + @Override + public void onSuccess(QueryInfo queryInfo) { + synchronized (queryInfoLock) { + gmaQueryInfo = queryInfo; + if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { + completeCreateQueryInfoSuccess(createQueryInfoCallbackDataPtr, queryInfo.getQuery()); + createQueryInfoCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeQueryInfoFutureCallback( + long internalPtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully generating a QueryInfo. */ + public static native void completeCreateQueryInfoSuccess( + long createQueryInfoInternalPtr, String query); + + /** + * Native callback invoked upon error generating a QueryInfo. Also used for wrapper/internal + * errors when processing a query info generation request. Returns integers representing + * firebase::gma::AdError codes. + */ + public static native void completeCreateQueryInfoError( + long createQueryInfoInternalPtr, int gmaErrorCode, String errorMessage); +} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java new file mode 100644 index 0000000000..3e78901d2f --- /dev/null +++ b/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java @@ -0,0 +1,345 @@ +/* + * Copyright 2021 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.gma.internal.cpp; + +import android.app.Activity; +import android.util.Log; +import com.google.android.gms.ads.AdError; +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdValue; +import com.google.android.gms.ads.FullScreenContentCallback; +import com.google.android.gms.ads.LoadAdError; +import com.google.android.gms.ads.OnPaidEventListener; +import com.google.android.gms.ads.OnUserEarnedRewardListener; +import com.google.android.gms.ads.ResponseInfo; +import com.google.android.gms.ads.rewarded.RewardItem; +import com.google.android.gms.ads.rewarded.RewardedAd; +import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; +import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions; + +/** + * Helper class to make interactions between the GMA C++ wrapper and Java {@link RewardedAd} objects + * cleaner. It's designed to wrap and adapt a single instance of {@link RewardedAd}, translate calls + * coming from C++ into their (typically more complicated) Java equivalents. + */ +public class RewardedAdHelper { + // C++ nullptr for use with the callbacks. + private static final long CPP_NULLPTR = 0; + + // Pointer to the RewardedAdInternalAndroid object that created this + // object. + private long mRewardedAdInternalPtr; + + // The GMA SDK {@link RewardedAd} associated with this helper. + private RewardedAd mRewarded; + + // Synchronization object for thread safe access to: + // * mRewarded + // * mRewardedAdInternalPtr + // * mLoadAdCallbackDataPtr + private final Object mRewardedLock; + + // The {@link Activity} this helper uses to display its + // {@link RewardedAd}. + private Activity mActivity; + + // The ad unit ID to use for the {@link RewardedAd}. + private String mAdUnitId; + + // Pointer to a FutureCallbackData in the C++ wrapper that will be used to + // complete the Future associated with the latest call to LoadAd. + private long mLoadAdCallbackDataPtr; + + /** Constructor. */ + public RewardedAdHelper(long rewardedAdInternalPtr) { + mRewardedAdInternalPtr = rewardedAdInternalPtr; + mRewardedLock = new Object(); + + // Test the callbacks and fail quickly if something's wrong. + completeRewardedAdFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); + } + + /** + * Initializes the {@link RewardedAd}. This creates the corresponding GMA SDK {@link RewardedAd} + * object and sets it up. + */ + public void initialize(final long callbackDataPtr, Activity activity) { + mActivity = activity; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + int errorCode; + String errorMessage; + synchronized (mRewardedLock) { + if (mRewarded == null) { + try { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + } catch (IllegalStateException e) { + mRewarded = null; + // This exception can be thrown if the ad unit ID was already set. + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; + } + completeRewardedAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + /** Disconnect the helper from the interstital ad. */ + public void disconnect() { + synchronized (mRewardedLock) { + mRewardedAdInternalPtr = CPP_NULLPTR; + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + + if (mActivity == null) { + return; + } + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mRewardedLock) { + if (mRewarded != null) { + mRewarded.setFullScreenContentCallback(null); + mRewarded.setOnPaidEventListener(null); + mRewarded = null; + } + } + } + }); + } + + /** Loads an ad for the underlying {@link RewardedAd} object. */ + public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { + if (mActivity == null) { + return; + } + synchronized (mRewardedLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeRewardedLoadAdInternalError(callbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); + return; + } + mLoadAdCallbackDataPtr = callbackDataPtr; + } + + mAdUnitId = adUnitId; + + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mRewardedLock) { + if (mActivity == null) { + completeRewardedLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } else { + try { + RewardedAd.load(mActivity, mAdUnitId, request, new RewardedAdListener()); + } catch (IllegalStateException e) { + completeRewardedLoadAdInternalError(mLoadAdCallbackDataPtr, + ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, + ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + }); + } + + /** + * Shows a previously loaded ad. + */ + public void show(final long callbackDataPtr, final String verificationCustomData, + final String verificationUserId) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + synchronized (mRewardedLock) { + int errorCode; + String errorMessage; + if (mAdUnitId == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; + } else if (mRewarded == null) { + errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; + } else { + errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; + errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; + if (!verificationCustomData.isEmpty() || !verificationUserId.isEmpty()) { + ServerSideVerificationOptions options = new ServerSideVerificationOptions.Builder() + .setCustomData(verificationCustomData) + .setUserId(verificationUserId) + .build(); + mRewarded.setServerSideVerificationOptions(options); + } + mRewarded.show(mActivity, new UserEarnedRewardListener()); + } + completeRewardedAdFutureCallback(callbackDataPtr, errorCode, errorMessage); + } + } + }); + } + + private class UserEarnedRewardListener implements OnUserEarnedRewardListener { + @Override + public void onUserEarnedReward(RewardItem rewardItem) { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyUserEarnedRewardEvent( + mRewardedAdInternalPtr, rewardItem.getType(), rewardItem.getAmount()); + } + } + } + } + + private class RewardedAdFullScreenContentListener + extends FullScreenContentCallback implements OnPaidEventListener { + @Override + public void onAdClicked() { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyAdClickedFullScreenContentEvent(mRewardedAdInternalPtr); + } + } + } + + @Override + public void onAdDismissedFullScreenContent() { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyAdDismissedFullScreenContentEvent(mRewardedAdInternalPtr); + } + } + } + + @Override + public void onAdFailedToShowFullScreenContent(AdError error) { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyAdFailedToShowFullScreenContentEvent(mRewardedAdInternalPtr, error); + } + } + } + + @Override + public void onAdImpression() { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyAdImpressionEvent(mRewardedAdInternalPtr); + } + } + } + + @Override + public void onAdShowedFullScreenContent() { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyAdShowedFullScreenContentEvent(mRewardedAdInternalPtr); + } + } + } + + public void onPaidEvent(AdValue value) { + synchronized (mRewardedLock) { + if (mRewardedAdInternalPtr != CPP_NULLPTR) { + notifyPaidEvent(mRewardedAdInternalPtr, value.getCurrencyCode(), value.getPrecisionType(), + value.getValueMicros()); + } + } + } + } + + private class RewardedAdListener extends RewardedAdLoadCallback { + @Override + public void onAdFailedToLoad(LoadAdError loadAdError) { + synchronized (mRewardedLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + completeRewardedLoadAdError( + mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + + @Override + public void onAdLoaded(RewardedAd ad) { + synchronized (mRewardedLock) { + if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { + mRewarded = ad; + RewardedAdFullScreenContentListener listener = new RewardedAdFullScreenContentListener(); + mRewarded.setFullScreenContentCallback(listener); + mRewarded.setOnPaidEventListener(listener); + completeRewardedLoadedAd(mLoadAdCallbackDataPtr, mRewarded.getResponseInfo()); + mLoadAdCallbackDataPtr = CPP_NULLPTR; + } + } + } + } + + /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ + public static native void completeRewardedAdFutureCallback( + long nativeInternalPtr, int errorCode, String errorMessage); + + /** Native callback invoked upon successfully loading an ad. */ + public static native void completeRewardedLoadedAd( + long nativeInternalPtr, ResponseInfo responseInfo); + + /** + * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile + * Ads SDK error codes. + */ + public static native void completeRewardedLoadAdError( + long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); + + /** + * Native callback upon encountering a wrapper/internal error when processing an Ad Request. + * Returns integers representing firebase::gma::AdError codes. + */ + public static native void completeRewardedLoadAdInternalError( + long nativeInternalPtr, int gmaErrorCode, String errorMessage); + + /** Native callbacks to notify the C++ wrapper of ad events */ + public static native void notifyUserEarnedRewardEvent( + long mRewardedAdInternalPtr, String type, int amount); + + public static native void notifyAdClickedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyAdDismissedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyAdFailedToShowFullScreenContentEvent( + long nativeInternalPtr, AdError adError); + + public static native void notifyAdImpressionEvent(long nativeInternalPtr); + + public static native void notifyAdShowedFullScreenContentEvent(long nativeInternalPtr); + + public static native void notifyPaidEvent( + long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); +} diff --git a/ump/ump_additional.pro b/ump/ump_additional.pro new file mode 100644 index 0000000000..57edbf9017 --- /dev/null +++ b/ump/ump_additional.pro @@ -0,0 +1,2 @@ +# Additional ProGuard rules needed for the AdMob library. +-keep class com.google.ads.mediation.admob.AdMobAdapter { *; } diff --git a/ump/ump_resources/AndroidManifest.xml b/ump/ump_resources/AndroidManifest.xml new file mode 100644 index 0000000000..66236bd0d6 --- /dev/null +++ b/ump/ump_resources/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/ump/ump_resources/build.gradle b/ump/ump_resources/build.gradle new file mode 100644 index 0000000000..62140c669a --- /dev/null +++ b/ump/ump_resources/build.gradle @@ -0,0 +1,70 @@ +// Copyright 2021 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. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.google.gms:google-services:4.4.1' + } +} +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + compileSdkVersion 34 + buildToolsVersion '32.0.0' + + defaultConfig { + minSdkVersion 23 + targetSdkVersion 34 + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java { + srcDirs = ['../src_java/com/google/firebase/gma/internal/cpp'] + } + } + } +} + +dependencies { + implementation platform('com.google.firebase:firebase-bom:33.11.0') + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.google.android.gms:play-services-ads:23.0.0' + implementation 'com.google.android.ump:user-messaging-platform:2.2.0' +} + +afterEvaluate { + generateReleaseBuildConfig.enabled = false +} + +apply from: "$rootDir/android_build_files/extract_and_dex.gradle" +extractAndDexAarFile('gma_resources') From 90ec0e168e03d3e98bcaa09e1a785f03f3a2a2cd Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:18:01 -0700 Subject: [PATCH 02/27] Move all the source files. --- ump/CMakeLists.txt | 13 +- .../consent_info_internal_android.cc | 0 .../{ump => }/consent_info_internal_android.h | 0 ump/src/common/{ump => }/consent_info.cc | 0 .../common/{ump => }/consent_info_internal.cc | 0 .../common/{ump => }/consent_info_internal.h | 0 ump/src/include/firebase/gma.h | 243 ----- ump/src/include/firebase/gma/ad_view.h | 272 ----- .../include/firebase/gma/internal/README.md | 6 - .../include/firebase/gma/internal/native_ad.h | 177 ---- .../firebase/gma/internal/query_info.h | 120 --- .../include/firebase/gma/interstitial_ad.h | 132 --- ump/src/include/firebase/gma/rewarded_ad.h | 156 --- ump/src/include/firebase/gma/types.h | 959 ------------------ ump/src/include/firebase/{gma => }/ump.h | 0 .../firebase/{gma => }/ump/consent_info.h | 0 .../include/firebase/{gma => }/ump/types.h | 0 .../ios/{ump => }/consent_info_internal_ios.h | 0 .../{ump => }/consent_info_internal_ios.mm | 0 .../{ump => }/consent_info_internal_stub.cc | 0 .../{ump => }/consent_info_internal_stub.h | 0 21 files changed, 5 insertions(+), 2073 deletions(-) rename ump/src/android/{ump => }/consent_info_internal_android.cc (100%) rename ump/src/android/{ump => }/consent_info_internal_android.h (100%) rename ump/src/common/{ump => }/consent_info.cc (100%) rename ump/src/common/{ump => }/consent_info_internal.cc (100%) rename ump/src/common/{ump => }/consent_info_internal.h (100%) delete mode 100644 ump/src/include/firebase/gma.h delete mode 100644 ump/src/include/firebase/gma/ad_view.h delete mode 100644 ump/src/include/firebase/gma/internal/README.md delete mode 100644 ump/src/include/firebase/gma/internal/native_ad.h delete mode 100644 ump/src/include/firebase/gma/internal/query_info.h delete mode 100644 ump/src/include/firebase/gma/interstitial_ad.h delete mode 100644 ump/src/include/firebase/gma/rewarded_ad.h delete mode 100644 ump/src/include/firebase/gma/types.h rename ump/src/include/firebase/{gma => }/ump.h (100%) rename ump/src/include/firebase/{gma => }/ump/consent_info.h (100%) rename ump/src/include/firebase/{gma => }/ump/types.h (100%) rename ump/src/ios/{ump => }/consent_info_internal_ios.h (100%) rename ump/src/ios/{ump => }/consent_info_internal_ios.mm (100%) rename ump/src/stub/{ump => }/consent_info_internal_stub.cc (100%) rename ump/src/stub/{ump => }/consent_info_internal_stub.h (100%) diff --git a/ump/CMakeLists.txt b/ump/CMakeLists.txt index 9365d784fd..5c24cf8e08 100644 --- a/ump/CMakeLists.txt +++ b/ump/CMakeLists.txt @@ -16,9 +16,8 @@ # Common source files used by all platforms set(common_SRCS - src/common/ump/consent_info.cc - src/common/ump/consent_info_internal.cc - src/common/gma_common.cc + src/common/consent_info.cc + src/common/consent_info_internal.cc ) # Define the resource build needed for Android @@ -32,19 +31,17 @@ binary_to_array("ump_resources" # Source files used by the Android implementation. set(android_SRCS ${ump_resources_source} - src/android/ump/consent_info_internal_android.cc - src/android/gma_android.cc + src/android/consent_info_internal_android.cc ) # Source files used by the iOS implementation. set(ios_SRCS - src/ios/ump/consent_info_internal_ios.mm - src/ios/gma_ios.mm + src/ios/consent_info_internal_ios.mm ) # Source files used by the stub implementation. set(stub_SRCS - src/stub/ump/consent_info_internal_stub.cc + src/stub/consent_info_internal_stub.cc ) if(ANDROID) diff --git a/ump/src/android/ump/consent_info_internal_android.cc b/ump/src/android/consent_info_internal_android.cc similarity index 100% rename from ump/src/android/ump/consent_info_internal_android.cc rename to ump/src/android/consent_info_internal_android.cc diff --git a/ump/src/android/ump/consent_info_internal_android.h b/ump/src/android/consent_info_internal_android.h similarity index 100% rename from ump/src/android/ump/consent_info_internal_android.h rename to ump/src/android/consent_info_internal_android.h diff --git a/ump/src/common/ump/consent_info.cc b/ump/src/common/consent_info.cc similarity index 100% rename from ump/src/common/ump/consent_info.cc rename to ump/src/common/consent_info.cc diff --git a/ump/src/common/ump/consent_info_internal.cc b/ump/src/common/consent_info_internal.cc similarity index 100% rename from ump/src/common/ump/consent_info_internal.cc rename to ump/src/common/consent_info_internal.cc diff --git a/ump/src/common/ump/consent_info_internal.h b/ump/src/common/consent_info_internal.h similarity index 100% rename from ump/src/common/ump/consent_info_internal.h rename to ump/src/common/consent_info_internal.h diff --git a/ump/src/include/firebase/gma.h b/ump/src/include/firebase/gma.h deleted file mode 100644 index 0d95f536f5..0000000000 --- a/ump/src/include/firebase/gma.h +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ - -#include "firebase/internal/platform.h" - -#if FIREBASE_PLATFORM_ANDROID -#include -#endif // FIREBASE_PLATFORM_ANDROID - -#include - -#include "firebase/app.h" -#include "firebase/gma/ad_view.h" -#include "firebase/gma/internal/native_ad.h" -#include "firebase/gma/internal/query_info.h" -#include "firebase/gma/interstitial_ad.h" -#include "firebase/gma/rewarded_ad.h" -#include "firebase/gma/types.h" -#include "firebase/internal/common.h" - -#if !defined(DOXYGEN) && !defined(SWIG) -FIREBASE_APP_REGISTER_CALLBACKS_REFERENCE(gma) -#endif // !defined(DOXYGEN) && !defined(SWIG) - -namespace firebase { -// In the GMA docs, link to firebase::Future in the Firebase C++ docs. -#if defined(DOXYGEN_ADMOB) -/// @brief The Google Mobile Ads C++ SDK uses this class to return results from -/// asynchronous operations. All C++ functions and method calls that operate -/// asynchronously return a %Future, and provide a "LastResult" -/// function to retrieve the most recent %Future result. -/// -/// The Google Mobile Ads C++ SDK uses this class from the Firebase C++ SDK to -/// return results from asynchronous operations. For more information, see the -/// Firebase -/// C++ SDK documentation. -template -class Future { - // Empty class (used for documentation only). -}; - -/// @brief Firebase App class. For more information, see the Firebase -/// C++ SDK documentation. -class App { - // Empty class (used for documentation only). -}; - -#endif // defined(DOXYGEN_ADMOB) - -/// @brief API for Google Mobile Ads with Firebase. -/// -/// The GMA API allows you to load and display mobile ads using the Google -/// Mobile Ads SDK. Each ad format has its own header file. -/// -/// @deprecated **The Google Mobile Ads (GMA) C++ SDK is _deprecated_ as of June -/// 17, 2024 and should not be adopted in projects that don't already use it. It -/// will enter _End-of-Maintenance (EoM)_ on June 17, 2025. Note that versions -/// of the SDK released before the EoM date will continue to function, but no -/// further bug fixes or changes will be released after the EoM date.** -/// -/// Instead of the Google Mobile Ads C++ SDK, consider using the -/// [iOS](/docs/admob/ios/quick-start) and -/// [Android](/docs/admob/android/quick-start) SDKs from AdMob. For support, -/// reach out to the [Google Mobile Ads SDK Technical -/// Forum](https://groups.google.com/g/google-admob-ads-sdk). -namespace gma { - -/// Initializes Google Mobile Ads (GMA) via Firebase. -/// -/// @param[in] app The Firebase app for which to initialize mobile ads. -/// -/// @param[out] init_result_out Optional: If provided, write the basic init -/// result here. kInitResultSuccess if initialization succeeded, or -/// kInitResultFailedMissingDependency on Android if Google Play services is not -/// available on the current device and the Google Mobile Ads SDK requires -/// Google Play services (for example, when using 'play-services-ads-lite'). -/// Note that this does not include the adapter initialization status, which is -/// returned in the Future. -/// -/// @return If init_result_out is kInitResultSuccess, this Future will contain -/// the initialization status of each adapter once initialization is complete. -/// Otherwise, the returned Future will have kFutureStatusInvalid. -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// the [SDK reference -/// documentation]( -/// /admob/cpp/reference/namespace/firebase/gma) -/// for more information. -FIREBASE_DEPRECATED Future Initialize( - const ::firebase::App& app, InitResult* init_result_out = nullptr); - -#if FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) -/// Initializes Google Mobile Ads (GMA) without Firebase for Android. -/// -/// The arguments to @ref Initialize are platform-specific so the caller must do -/// something like this: -/// @code -/// #if defined(__ANDROID__) -/// firebase::gma::Initialize(jni_env, activity); -/// #else -/// firebase::gma::Initialize(); -/// #endif -/// @endcode -/// -/// @param[in] jni_env JNIEnv pointer. -/// @param[in] activity Activity used to start the application. -/// @param[out] init_result_out Optional: If provided, write the basic init -/// result here. kInitResultSuccess if initialization succeeded, or -/// kInitResultFailedMissingDependency on Android if Google Play services is not -/// available on the current device and the Google Mobile Ads SDK requires -/// Google Play services (for example, when using 'play-services-ads-lite'). -/// Note that this does not include the adapter initialization status, which is -/// returned in the Future. -/// -/// @return If init_result_out is kInitResultSuccess, this Future will contain -/// the initialization status of each adapter once initialization is complete. -/// Otherwise, the returned Future will have kFutureStatusInvalid. -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma -/// for more information. -FIREBASE_DEPRECATED Future Initialize( - JNIEnv* jni_env, jobject activity, InitResult* init_result_out = nullptr); - -#endif // defined(__ANDROID__) || defined(DOXYGEN) -#if !FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) -/// Initializes Google Mobile Ads (GMA) without Firebase for iOS. -/// -/// @param[out] init_result_out Optional: If provided, write the basic init -/// result here. kInitResultSuccess if initialization succeeded, or -/// kInitResultFailedMissingDependency on Android if Google Play services is not -/// available on the current device and the Google Mobile Ads SDK requires -/// Google Play services (for example, when using 'play-services-ads-lite'). -/// Note that this does not include the adapter initialization status, which is -/// returned in the Future. -/// -/// @return If init_result_out is kInitResultSuccess, this Future -/// will contain the initialization status of each adapter once initialization -/// is complete. Otherwise, the returned Future will have -/// kFutureStatusInvalid. -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma -/// for more information. -FIREBASE_DEPRECATED Future Initialize( - InitResult* init_result_out = nullptr); -#endif // !defined(__ANDROID__) || defined(DOXYGEN) - -/// Get the Future returned by a previous call to -/// @ref firebase::gma::Initialize(). -Future InitializeLastResult(); - -/// Get the current adapter initialization status. You can poll this method to -/// check which adapters have been initialized. -AdapterInitializationStatus GetInitializationStatus(); - -/// Disables automated SDK crash reporting on iOS. If not called, the SDK -/// records the original exception handler if available and registers a new -/// exception handler. The new exception handler only reports SDK related -/// exceptions and calls the recorded original exception handler. -/// -/// This method has no effect on Android. -void DisableSDKCrashReporting(); - -/// Disables mediation adapter initialization on iOS during initialization of -/// the GMA SDK. Calling this method may negatively impact your ad -/// performance and should only be called if you will not use GMA SDK -/// controlled mediation during this app session. This method must be called -/// before initializing the GMA SDK or loading ads and has no effect once the -/// SDK has been initialized. -/// -/// This method has no effect on Android. -void DisableMediationInitialization(); - -/// Sets the global @ref RequestConfiguration that will be used for -/// every @ref AdRequest during the app's session. -/// -/// @param[in] request_configuration The request configuration that should be -/// applied to all ad requests. -void SetRequestConfiguration(const RequestConfiguration& request_configuration); - -/// Gets the global RequestConfiguration. -/// -/// @return the currently active @ref RequestConfiguration that's being -/// used for every ad request. -/// @note: on iOS, the -/// @ref RequestConfiguration::tag_for_child_directed_treatment and -/// @ref RequestConfiguration::tag_for_under_age_of_consent fields will be set -/// to RequestConfiguration.kChildDirectedTreatmentUnspecified, and -/// RequestConfiguration.kUnderAgeOfConsentUnspecified, respectfully. -RequestConfiguration GetRequestConfiguration(); - -/// Opens the ad inspector UI. -/// -/// @param[in] parent The platform-specific UI element that will host the -/// ad inspector. For iOS this should be the window's -/// UIViewController. For Android this is the -/// Activity Context which the GMA SDK is running in. -/// @param[in] listener The listener will be invoked when the user closes -/// the ad inspector UI. @ref firebase::gma::Initialize(). must be called -/// prior to this function. -void OpenAdInspector(AdParent parent, AdInspectorClosedListener* listener); - -/// Controls whether the Google Mobile Ads SDK Same App Key is enabled. -/// -/// This function must be invoked after GMA has been initialized. The value set -/// persists across app sessions. The key is enabled by default. -/// -/// This operation is supported on iOS only. This is a no-op on Android -/// systems. -/// -/// @param[in] is_enabled whether the Google Mobile Ads SDK Same App Key is -/// enabled. -void SetIsSameAppKeyEnabled(bool is_enabled); - -/// @brief Terminate GMA. -/// -/// Frees resources associated with GMA that were allocated during -/// @ref firebase::gma::Initialize(). -void Terminate(); - -} // namespace gma -} // namespace firebase - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_H_ diff --git a/ump/src/include/firebase/gma/ad_view.h b/ump/src/include/firebase/gma/ad_view.h deleted file mode 100644 index d2d73db67d..0000000000 --- a/ump/src/include/firebase/gma/ad_view.h +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ - -#include "firebase/future.h" -#include "firebase/gma/types.h" -#include "firebase/internal/common.h" - -namespace firebase { -namespace gma { - -namespace internal { -// Forward declaration for platform-specific data, implemented in each library. -class AdViewInternal; -} // namespace internal - -class AdViewBoundingBoxListener; -struct BoundingBox; - -/// @brief Loads and displays Google Mobile Ads AdView ads. -/// -/// Each AdView object corresponds to a single GMA ad placement of a specified -/// size. There are methods to load an ad, move it, show it and hide it, and -/// retrieve the bounds of the ad onscreen. -/// -/// AdView objects provide information about their current state through -/// Futures. Methods like @ref Initialize, @ref LoadAd, and @ref Hide each have -/// a corresponding @ref Future from which the result of the last call can be -/// determined. The two variants of @ref SetPosition share a single result -/// @ref Future, since they're essentially the same action. -/// -/// For example, you could initialize, load, and show an AdView while -/// checking the result of the previous action at each step as follows: -/// -/// @code -/// namespace gma = ::firebase::gma; -/// gma::AdView* ad_view = new gma::AdView(); -/// ad_view->Initialize(ad_parent, "YOUR_AD_UNIT_ID", desired_ad_size) -/// @endcode -/// -/// Then, later: -/// -/// @code -/// if (ad_view->InitializeLastResult().status() == -/// ::firebase::kFutureStatusComplete && -/// ad_view->InitializeLastResult().error() == -/// firebase::gma::kAdErrorCodeNone) { -/// ad_view->LoadAd(your_ad_request); -/// } -/// @endcode -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma -/// for more information. -class AdView { - public: - /// The possible screen positions for a @ref AdView, configured via - /// @ref SetPosition. - enum Position { - /// The position isn't one of the predefined screen locations. - kPositionUndefined = -1, - /// Top of the screen, horizontally centered. - kPositionTop = 0, - /// Bottom of the screen, horizontally centered. - kPositionBottom, - /// Top-left corner of the screen. - kPositionTopLeft, - /// Top-right corner of the screen. - kPositionTopRight, - /// Bottom-left corner of the screen. - kPositionBottomLeft, - /// Bottom-right corner of the screen. - kPositionBottomRight, - }; - - /// Creates an uninitialized @ref AdView object. - /// @ref Initialize must be called before the object is used. - FIREBASE_DEPRECATED AdView(); - - ~AdView(); - - /// Initializes the @ref AdView object. - /// @param[in] parent The platform-specific UI element that will host the ad. - /// @param[in] ad_unit_id The ad unit ID to use when requesting ads. - /// @param[in] size The desired ad size for the ad. - FIREBASE_DEPRECATED Future Initialize(AdParent parent, - const char* ad_unit_id, - const AdSize& size); - - /// Returns a @ref Future that has the status of the last call to - /// @ref Initialize. - FIREBASE_DEPRECATED Future InitializeLastResult() const; - - /// Begins an asynchronous request for an ad. If successful, the ad will - /// automatically be displayed in the AdView. - /// @param[in] request An AdRequest struct with information about the request - /// to be made (such as targeting info). - Future LoadAd(const AdRequest& request); - - /// Returns a @ref Future containing the status of the last call to - /// @ref LoadAd. - Future LoadAdLastResult() const; - - /// Retrieves the @ref AdView's current onscreen size and location. - /// - /// @return The current size and location. Values are in pixels, and location - /// coordinates originate from the top-left corner of the screen. - BoundingBox bounding_box() const; - - /// Sets an AdListener for this ad view. - /// - /// @param[in] listener An AdListener object which will be invoked - /// when lifecycle events occur on this AdView. - void SetAdListener(AdListener* listener); - - /// Sets a listener to be invoked when the Ad's bounding box - /// changes size or location. - /// - /// @param[in] listener A AdViewBoundingBoxListener object which will be - /// invoked when the ad changes size, shape, or position. - void SetBoundingBoxListener(AdViewBoundingBoxListener* listener); - - /// Sets a listener to be invoked when this ad is estimated to have earned - /// money. - /// - /// @param[in] listener A PaidEventListener object to be invoked when a - /// paid event occurs on the ad. - void SetPaidEventListener(PaidEventListener* listener); - - /// Moves the @ref AdView so that its top-left corner is located at - /// (x, y). Coordinates are in pixels from the top-left corner of the screen. - /// - /// When built for Android, the library will not display an ad on top of or - /// beneath an Activity's status bar. If a call to SetPosition - /// would result in an overlap, the @ref AdView is placed just below the - /// status bar, so no overlap occurs. - /// @param[in] x The desired horizontal coordinate. - /// @param[in] y The desired vertical coordinate. - /// - /// @return a @ref Future which will be completed when this move operation - /// completes. - Future SetPosition(int x, int y); - - /// Moves the @ref AdView so that it's located at the given predefined - /// position. - /// - /// @param[in] position The predefined position to which to move the - /// @ref AdView. - /// - /// @return a @ref Future which will be completed when this move operation - /// completes. - Future SetPosition(Position position); - - /// Returns a @ref Future containing the status of the last call to either - /// version of @ref SetPosition. - Future SetPositionLastResult() const; - - /// Hides the AdView. - Future Hide(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Hide. - Future HideLastResult() const; - - /// Shows the @ref AdView. - Future Show(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Show. - Future ShowLastResult() const; - - /// Pauses the @ref AdView. Should be called whenever the C++ engine - /// pauses or the application loses focus. - Future Pause(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Pause. - Future PauseLastResult() const; - - /// Resumes the @ref AdView after pausing. - Future Resume(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Resume. - Future ResumeLastResult() const; - - /// Cleans up and deallocates any resources used by the @ref AdView. - /// You must call this asynchronous operation before this object's destructor - /// is invoked or risk leaking device resources. - Future Destroy(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Destroy. - Future DestroyLastResult() const; - - /// Returns the AdSize of the AdView. - /// - /// @return An @ref AdSize object representing the size of the ad. If this - /// view has not been initialized then the AdSize will be 0,0. - AdSize ad_size() const; - - protected: - /// Pointer to a listener for AdListener events. - AdListener* ad_listener_; - - /// Pointer to a listener for BoundingBox events. - AdViewBoundingBoxListener* ad_view_bounding_box_listener_; - - /// Pointer to a listener for paid events. - PaidEventListener* paid_event_listener_; - - private: - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - internal::AdViewInternal* internal_; -}; - -/// A listener class that developers can extend and pass to an @ref AdView -/// object's @ref AdView::SetBoundingBoxListener method to be notified of -/// changes to the size of the Ad's bounding box. -class AdViewBoundingBoxListener { - public: - virtual ~AdViewBoundingBoxListener(); - - /// This method is called when the @ref AdView object's bounding box - /// changes. - /// - /// @param[in] ad_view The view whose bounding box changed. - /// @param[in] box The new bounding box. - virtual void OnBoundingBoxChanged(AdView* ad_view, BoundingBox box) = 0; -}; - -/// @brief The screen location and dimensions of an AdView once it has been -/// initialized. -struct BoundingBox { - /// Default constructor which initializes all member variables to 0. - BoundingBox() - : height(0), width(0), x(0), y(0), position(AdView::kPositionUndefined) {} - - /// Height of the ad in pixels. - int height; - /// Width of the ad in pixels. - int width; - /// Horizontal position of the ad in pixels from the left. - int x; - /// Vertical position of the ad in pixels from the top. - int y; - - /// The position of the AdView if one has been set as the target position, or - /// kPositionUndefined otherwise. - AdView::Position position; -}; - -} // namespace gma -} // namespace firebase - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_AD_VIEW_H_ diff --git a/ump/src/include/firebase/gma/internal/README.md b/ump/src/include/firebase/gma/internal/README.md deleted file mode 100644 index ce08eafee1..0000000000 --- a/ump/src/include/firebase/gma/internal/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Firebase C++ Open Source Development - -**NOTE: For internal use only.** - -This folder contains experimental header files that are internal and experimental. -Users shouldn't expect any stability guarantees for code in this folder, and any public usage is discouraged and is not supported. diff --git a/ump/src/include/firebase/gma/internal/native_ad.h b/ump/src/include/firebase/gma/internal/native_ad.h deleted file mode 100644 index a35d8f90b4..0000000000 --- a/ump/src/include/firebase/gma/internal/native_ad.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ - -#include -#include - -#include "firebase/future.h" -#include "firebase/gma/types.h" -#include "firebase/internal/common.h" -#include "firebase/variant.h" - -// Doxygen breaks trying to parse this file, and since it is internal logic, -// it doesn't need to be included in the generated documentation. -#ifndef DOXYGEN - -namespace firebase { -namespace gma { - -namespace internal { -// Forward declaration for platform-specific data, implemented in each library. -class NativeAdInternal; -} // namespace internal - -struct NativeAdImageInternal; - -class GmaInternal; -class NativeAdImage; -class ImageResult; - -class NativeAd { - public: - NativeAd(); - ~NativeAd(); - - /// Initialize the NativeAd object. - /// parent: The platform-specific UI element that will host the ad. - Future Initialize(AdParent parent); - - /// Returns a Future containing the status of the last call to - /// Initialize. - Future InitializeLastResult() const; - - /// Begins an asynchronous request for an ad. - /// - /// ad_unit_id: The ad unit ID to use in loading the ad. - /// request: An AdRequest struct with information about the request - /// to be made (such as targeting info). - Future LoadAd(const char* ad_unit_id, const AdRequest& request); - - /// Returns a Future containing the status of the last call to - /// LoadAd. - Future LoadAdLastResult() const; - - /// Sets an AdListener for this native ad. - /// - /// @param[in] listener An AdListener object which will be invoked - /// when lifecycle events occur on this NativeAd. - void SetAdListener(AdListener* listener); - - /// Returns the associated icon asset of the native ad. - const NativeAdImage& icon() const; - - /// Returns the associated image assets of the native ad. - const std::vector& images() const; - - // Returns the associated adchoices icon asset of the native ad. - const NativeAdImage& adchoices_icon() const; - - /// Only allowlisted ad units use this api. - Future RecordImpression(const Variant& impression_data); - - /// Returns a Future containing the status of the last call to - /// RecordImpression. - Future RecordImpressionLastResult() const; - - /// Only allowlisted ad units use this api. - Future PerformClick(const Variant& click_data); - - /// Returns a Future containing the status of the last call to - /// PerformClick. - Future PerformClickLastResult() const; - - private: - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - internal::NativeAdInternal* internal_; -}; - -/// Information about the result of a load image operation. -class ImageResult { - public: - /// Default Constructor. - ImageResult(); - - /// Destructor. - virtual ~ImageResult(); - - /// Returns true if the operation was successful. - bool is_successful() const; - - /// If the ImageResult::is_successful() returned false, then the - /// vector returned via this method will contain no contextual - /// information. - const std::vector& image() const; - - private: - friend class GmaInternal; - - /// Constructor invoked upon successful image load. - explicit ImageResult(const std::vector& image_info); - - /// Denotes if the ImageResult represents a success or an error. - bool is_successful_; - - /// Contains the loaded image asset. - std::vector image_info_; -}; - -class NativeAdImage { - public: - /// Default Constructor. - NativeAdImage(); - - /// Copy Constructor. - NativeAdImage(const NativeAdImage& source_native_image); - - /// Returns the image scale, which denotes the ratio of pixels to dp. - double scale() const; - - /// Returns the image uri. - const std::string& image_uri() const; - - /// Begins an asynchronous request for loading an image asset. - Future LoadImage() const; - - // Returns a Future containing the status of the last call to - // LoadImage. - Future LoadImageLastResult() const; - - virtual ~NativeAdImage(); - - /// Assignment operator. - NativeAdImage& operator=(const NativeAdImage& obj); - - private: - friend class NativeAd; - friend class GmaInternal; - - explicit NativeAdImage(const NativeAdImageInternal& native_ad_image_internal); - - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - NativeAdImageInternal* internal_; -}; - -} // namespace gma -} // namespace firebase - -#endif // DOXYGEN - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_NATIVE_AD_H_ diff --git a/ump/src/include/firebase/gma/internal/query_info.h b/ump/src/include/firebase/gma/internal/query_info.h deleted file mode 100644 index b2d77959dc..0000000000 --- a/ump/src/include/firebase/gma/internal/query_info.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2024 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ - -#include - -#include "firebase/future.h" -#include "firebase/gma/types.h" - -// Doxygen breaks trying to parse this file, and since it is internal logic, -// it doesn't need to be included in the generated documentation. -#ifndef DOXYGEN - -namespace firebase { -namespace gma { - -namespace internal { -// Forward declaration for platform-specific data, implemented in each library. -class QueryInfoInternal; -} // namespace internal - -class GmaInternal; -class QueryInfoResult; - -class QueryInfo { - public: - QueryInfo(); - ~QueryInfo(); - - /// Initialize the QueryInfo object. - /// parent: The platform-specific application context. - Future Initialize(AdParent parent); - - /// Returns a Future containing the status of the last call to - /// Initialize. - Future InitializeLastResult() const; - - /// Begins an asynchronous request for creating a query info string. - /// - /// format: The format of the ad for which the query info is being created. - /// request: An AdRequest struct with information about the request - /// to be made (such as targeting info). - Future CreateQueryInfo(AdFormat format, - const AdRequest& request); - - /// Returns a Future containing the status of the last call to - /// CreateQueryInfo. - Future CreateQueryInfoLastResult() const; - - /// Begins an asynchronous request for creating a query info string. - /// - /// format: The format of the ad for which the query info is being created. - /// request: An AdRequest struct with information about the request - /// to be made (such as targeting info). - /// ad_unit_id: The ad unit ID to use in loading the ad. - Future CreateQueryInfoWithAdUnit(AdFormat format, - const AdRequest& request, - const char* ad_unit_id); - - /// Returns a Future containing the status of the last call to - /// CreateQueryInfoWithAdUnit. - Future CreateQueryInfoWithAdUnitLastResult() const; - - private: - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - internal::QueryInfoInternal* internal_; -}; - -/// Information about the result of a create queryInfo operation. -class QueryInfoResult { - public: - /// Default Constructor. - QueryInfoResult(); - - /// Destructor. - virtual ~QueryInfoResult(); - - /// Returns true if the operation was successful. - bool is_successful() const; - - /// If the QueryInfoResult::is_successful() returned false, then the - /// string returned via this method will contain no contextual - /// information. - const std::string& query_info() const; - - private: - friend class GmaInternal; - - /// Constructor invoked upon successful query info generation. - explicit QueryInfoResult(const std::string& query_info); - - /// Denotes if the QueryInfoResult represents a success or an error. - bool is_successful_; - - /// Contains the full query info string. - std::string query_info_; -}; - -} // namespace gma -} // namespace firebase - -#endif // DOXYGEN - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERNAL_QUERY_INFO_H_ diff --git a/ump/src/include/firebase/gma/interstitial_ad.h b/ump/src/include/firebase/gma/interstitial_ad.h deleted file mode 100644 index 4173209f42..0000000000 --- a/ump/src/include/firebase/gma/interstitial_ad.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ - -#include "firebase/future.h" -#include "firebase/gma/types.h" -#include "firebase/internal/common.h" - -namespace firebase { -namespace gma { - -namespace internal { -// Forward declaration for platform-specific data, implemented in each library. -class InterstitialAdInternal; -} // namespace internal - -/// @brief Loads and displays Google Mobile Ads interstitial ads. -/// -/// @ref InterstitialAd is a single-use object that can load and show a -/// single GMA interstitial ad. -/// -/// InterstitialAd objects provide information about their current state -/// through Futures. @ref Initialize, @ref LoadAd, and @ref Show each have a -/// corresponding @ref Future from which you can determine result of the -/// previous call. -/// -/// Here's how one might initialize, load, and show an interstitial ad while -/// checking against the result of the previous action at each step: -/// -/// @code -/// namespace gma = ::firebase::gma; -/// gma::InterstitialAd* interstitial = new gma::InterstitialAd(); -/// interstitial->Initialize(ad_parent); -/// @endcode -/// -/// Then, later: -/// -/// @code -/// if (interstitial->InitializeLastResult().status() == -/// ::firebase::kFutureStatusComplete && -/// interstitial->InitializeLastResult().error() == -/// firebase::gma::kAdErrorCodeNone) { -/// interstitial->LoadAd( "YOUR_AD_UNIT_ID", my_ad_request); -/// } -/// @endcode -/// -/// And after that: -/// -/// @code -/// if (interstitial->LoadAdLastResult().status() == -/// ::firebase::kFutureStatusComplete && -/// interstitial->LoadAdLastResult().error() == -/// firebase::gma::kAdErrorCodeNone)) { -/// interstitial->Show(); -/// } -/// @endcode -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma -/// for more information. -class InterstitialAd { - public: - /// Creates an uninitialized @ref InterstitialAd object. - /// @ref Initialize must be called before the object is used. - FIREBASE_DEPRECATED InterstitialAd(); - - ~InterstitialAd(); - - /// Initialize the @ref InterstitialAd object. - /// @param[in] parent The platform-specific UI element that will host the ad. - FIREBASE_DEPRECATED Future Initialize(AdParent parent); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Initialize. - FIREBASE_DEPRECATED Future InitializeLastResult() const; - - /// Begins an asynchronous request for an ad. - /// - /// @param[in] ad_unit_id The ad unit ID to use in loading the ad. - /// @param[in] request An AdRequest struct with information about the request - /// to be made (such as targeting info). - Future LoadAd(const char* ad_unit_id, const AdRequest& request); - - /// Returns a @ref Future containing the status of the last call to - /// @ref LoadAd. - Future LoadAdLastResult() const; - - /// Shows the @ref InterstitialAd. This should not be called unless an ad has - /// already been loaded. - Future Show(); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Show. - Future ShowLastResult() const; - - /// Sets the @ref FullScreenContentListener for this @ref InterstitialAd. - /// - /// @param[in] listener A valid @ref FullScreenContentListener to receive - /// callbacks. - void SetFullScreenContentListener(FullScreenContentListener* listener); - - /// Registers a callback to be invoked when this ad is estimated to have - /// earned money - /// - /// @param[in] listener A valid @ref PaidEventListener to receive callbacks. - void SetPaidEventListener(PaidEventListener* listener); - - private: - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - internal::InterstitialAdInternal* internal_; -}; - -} // namespace gma -} // namespace firebase - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_INTERSTITIAL_AD_H_ diff --git a/ump/src/include/firebase/gma/rewarded_ad.h b/ump/src/include/firebase/gma/rewarded_ad.h deleted file mode 100644 index 6aaafbb798..0000000000 --- a/ump/src/include/firebase/gma/rewarded_ad.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ - -#include - -#include "firebase/future.h" -#include "firebase/gma/types.h" -#include "firebase/internal/common.h" - -namespace firebase { -namespace gma { - -namespace internal { -// Forward declaration for platform-specific data, implemented in each library. -class RewardedAdInternal; -} // namespace internal - -/// @brief Loads and displays Google Mobile Ads rewarded ads. -/// -/// @ref RewardedAd is a single-use object that can load and show a -/// single GMA rewarded ad. -/// -/// RewardedAd objects provide information about their current state -/// through Futures. @ref Initialize, @ref LoadAd, and @ref Show each have a -/// corresponding @ref Future from which you can determine result of the -/// previous call. -/// -/// Here's how one might initialize, load, and show an rewarded ad while -/// checking against the result of the previous action at each step: -/// -/// @code -/// namespace gma = ::firebase::gma; -/// gma::RewardedAd* rewarded = new gma::RewardedAd(); -/// rewarded->Initialize(ad_parent); -/// @endcode -/// -/// Then, later: -/// -/// @code -/// if (rewarded->InitializeLastResult().status() == -/// ::firebase::kFutureStatusComplete && -/// rewarded->InitializeLastResult().error() == -/// firebase::gma::kAdErrorCodeNone) { -/// rewarded->LoadAd( "YOUR_AD_UNIT_ID", my_ad_request); -/// } -/// @endcode -/// -/// And after that: -/// -/// @code -/// if (rewarded->LoadAdLastResult().status() == -/// ::firebase::kFutureStatusComplete && -/// rewarded->LoadAdLastResult().error() == -/// firebase::gma::kAdErrorCodeNone)) { -/// rewarded->Show(&my_user_earned_reward_listener); -/// } -/// @endcode -/// -/// @deprecated The Google Mobile Ads C++ SDK is now deprecated. Please see -/// https://developers.google.com/admob/cpp/reference/namespace/firebase/gma -/// for more information. -class RewardedAd { - public: - /// Options for RewardedAd server-side verification callbacks. Set options on - /// a RewardedAd object using the @ref SetServerSideVerificationOptions - /// method. - struct ServerSideVerificationOptions { - /// Custom data to be included in server-side verification callbacks. - std::string custom_data; - - /// User id to be used in server-to-server reward callbacks. - std::string user_id; - }; - - /// Creates an uninitialized @ref RewardedAd object. - /// @ref Initialize must be called before the object is used. - FIREBASE_DEPRECATED RewardedAd(); - - ~RewardedAd(); - - /// Initialize the @ref RewardedAd object. - /// @param[in] parent The platform-specific UI element that will host the ad. - FIREBASE_DEPRECATED Future Initialize(AdParent parent); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Initialize. - FIREBASE_DEPRECATED Future InitializeLastResult() const; - - /// Begins an asynchronous request for an ad. - /// - /// @param[in] ad_unit_id The ad unit ID to use in loading the ad. - /// @param[in] request An AdRequest struct with information about the request - /// to be made (such as targeting info). - Future LoadAd(const char* ad_unit_id, const AdRequest& request); - - /// Returns a @ref Future containing the status of the last call to - /// @ref LoadAd. - Future LoadAdLastResult() const; - - /// Shows the @ref RewardedAd. This should not be called unless an ad has - /// already been loaded. - /// - /// @param[in] listener The @ref UserEarnedRewardListener to be notified when - /// user earns a reward. - Future Show(UserEarnedRewardListener* listener); - - /// Returns a @ref Future containing the status of the last call to - /// @ref Show. - Future ShowLastResult() const; - - /// Sets the @ref FullScreenContentListener for this @ref RewardedAd. - /// - /// @param[in] listener A valid @ref FullScreenContentListener to receive - /// callbacks. - void SetFullScreenContentListener(FullScreenContentListener* listener); - - /// Registers a callback to be invoked when this ad is estimated to have - /// earned money - /// - /// @param[in] listener A valid @ref PaidEventListener to receive callbacks. - void SetPaidEventListener(PaidEventListener* listener); - - /// Sets the server side verification options. - /// - /// @param[in] serverSideVerificationOptions A @ref - /// ServerSideVerificationOptions object containing custom data and a user - /// Id. - void SetServerSideVerificationOptions( - const ServerSideVerificationOptions& serverSideVerificationOptions); - - private: - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - internal::RewardedAdInternal* internal_; -}; - -} // namespace gma -} // namespace firebase - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_REWARDED_AD_H_ diff --git a/ump/src/include/firebase/gma/types.h b/ump/src/include/firebase/gma/types.h deleted file mode 100644 index be568f2146..0000000000 --- a/ump/src/include/firebase/gma/types.h +++ /dev/null @@ -1,959 +0,0 @@ -/* - * Copyright 2021 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. - */ - -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ - -#include -#include -#include -#include -#include - -#include "firebase/future.h" -#include "firebase/internal/platform.h" - -#if FIREBASE_PLATFORM_ANDROID -#include -#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS -extern "C" { -#include -} // extern "C" -#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, - // FIREBASE_PLATFORM_TVOS - -namespace firebase { -namespace gma { - -struct AdErrorInternal; -struct AdapterResponseInfoInternal; -struct BoundingBox; -struct ResponseInfoInternal; - -class AdapterResponseInfo; -class AdViewBoundingBoxListener; -class GmaInternal; -class AdView; -class InterstitialAd; -class PaidEventListener; -class ResponseInfo; - -namespace internal { -class AdViewInternal; -} - -/// This is a platform specific datatype that is required to create -/// a Google Mobile Ads ad. -/// -/// The following defines the datatype on each platform: -///

    -///
  • Android: A `jobject` which references an Android Activity.
  • -///
  • iOS: An `id` which references an iOS UIView.
  • -///
-#if FIREBASE_PLATFORM_ANDROID -/// An Android Activity from Java. -typedef jobject AdParent; -#elif FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_TVOS -/// A pointer to an iOS UIView. -typedef id AdParent; -#else -/// A void pointer for stub classes. -typedef void* AdParent; -#endif // FIREBASE_PLATFORM_ANDROID, FIREBASE_PLATFORM_IOS, - // FIREBASE_PLATFORM_TVOS - -/// Error codes returned by Future::error(). -enum AdErrorCode { - /// Call completed successfully. - kAdErrorCodeNone, - /// The ad has not been fully initialized. - kAdErrorCodeUninitialized, - /// The ad is already initialized (repeat call). - kAdErrorCodeAlreadyInitialized, - /// A call has failed because an ad is currently loading. - kAdErrorCodeLoadInProgress, - /// A call to load an ad has failed due to an internal SDK error. - kAdErrorCodeInternalError, - /// A call to load an ad has failed due to an invalid request. - kAdErrorCodeInvalidRequest, - /// A call to load an ad has failed due to a network error. - kAdErrorCodeNetworkError, - /// A call to load an ad has failed because no ad was available to serve. - kAdErrorCodeNoFill, - /// An attempt has been made to show an ad on an Android Activity that has - /// no window token (such as one that's not done initializing). - kAdErrorCodeNoWindowToken, - /// An attempt to load an Ad Network extras class for an ad request has - /// failed. - kAdErrorCodeAdNetworkClassLoadError, - /// The ad server experienced a failure processing the request. - kAdErrorCodeServerError, - /// The current device’s OS is below the minimum required version. - kAdErrorCodeOSVersionTooLow, - /// The request was unable to be loaded before being timed out. - kAdErrorCodeTimeout, - /// Will not send request because the interstitial object has already been - /// used. - kAdErrorCodeInterstitialAlreadyUsed, - /// The mediation response was invalid. - kAdErrorCodeMediationDataError, - /// Error finding or creating a mediation ad network adapter. - kAdErrorCodeMediationAdapterError, - /// Attempting to pass an invalid ad size to an adapter. - kAdErrorCodeMediationInvalidAdSize, - /// Invalid argument error. - kAdErrorCodeInvalidArgument, - /// Received invalid response. - kAdErrorCodeReceivedInvalidResponse, - /// Will not send a request because the rewarded ad object has already been - /// used. - kAdErrorCodeRewardedAdAlreadyUsed, - /// A mediation ad network adapter received an ad request, but did not fill. - /// The adapter’s error is included as an underlyingError. - kAdErrorCodeMediationNoFill, - /// Will not send request because the ad object has already been used. - kAdErrorCodeAdAlreadyUsed, - /// Will not send request because the application identifier is missing. - kAdErrorCodeApplicationIdentifierMissing, - /// Android Ad String is invalid. - kAdErrorCodeInvalidAdString, - /// The ad can not be shown when app is not in the foreground. - kAdErrorCodeAppNotInForeground, - /// A mediation adapter failed to show the ad. - kAdErrorCodeMediationShowError, - /// The ad is not ready to be shown. - kAdErrorCodeAdNotReady, - /// Ad is too large for the scene. - kAdErrorCodeAdTooLarge, - /// Attempted to present ad from a non-main thread. This is an internal - /// error which should be reported to support if encountered. - kAdErrorCodeNotMainThread, - /// A debug operation failed because the device is not in test mode. - kAdErrorCodeNotInTestMode, - /// An attempt to load the Ad Inspector failed. - kAdErrorCodeInspectorFailedToLoad, - /// The request to show the Ad Inspector failed because it's already open. - kAdErrorCodeInsepctorAlreadyOpen, - /// Error processing image url. - kAdErrorCodeImageUrlMalformed, - /// Fallback error for any unidentified cases. - kAdErrorCodeUnknown, -}; - -#if !defined(DOXYGEN) -/// Format of the ad being requested. Currently used only for internal updates. -enum AdFormat { - /// App open ad format. - kAdFormatAppOpen, - /// Banner ad format. - kAdFormatBanner, - /// Interstitial ad format. - kAdFormatInterstitial, - /// Native ad format. - kAdFormatNative, - /// Rewarded ad format. - kAdFormatRewarded, - /// Rewarded interstitial ad format. - kAdFormatRewardedInterstitial, -}; -#endif // !defined(DOXYGEN) - -/// A listener for receiving notifications during the lifecycle of a BannerAd. -class AdListener { - public: - virtual ~AdListener(); - - /// Called when a click is recorded for an ad. - virtual void OnAdClicked() {} - - /// Called when the user is about to return to the application after clicking - /// on an ad. - virtual void OnAdClosed() {} - - /// Called when an impression is recorded for an ad. - virtual void OnAdImpression() {} - - /// Called when an ad opens an overlay that covers the screen. - virtual void OnAdOpened() {} -}; - -/// Information about why an ad operation failed. -class AdError { - public: - /// Default Constructor. - AdError(); - - /// Copy Constructor. - AdError(const AdError& ad_error); - - /// Destructor. - virtual ~AdError(); - - /// Assignment operator. - AdError& operator=(const AdError& obj); - - /// Retrieves an AdError which represents the cause of this error. - /// - /// @return a pointer to an adError which represents the cause of this - /// AdError. If there was no cause then nullptr is returned. - std::unique_ptr GetCause() const; - - /// Gets the error's code. - AdErrorCode code() const; - - /// Gets the domain of the error. - const std::string& domain() const; - - /// Gets the message describing the error. - const std::string& message() const; - - /// Gets the ResponseInfo if an error occurred during a loadAd operation. - /// The ResponseInfo will have empty fields if this AdError does not - /// represent an error stemming from a load ad operation. - const ResponseInfo& response_info() const; - - /// Returns a log friendly string version of this object. - virtual const std::string& ToString() const; - - /// A domain string which represents an undefined error domain. - /// - /// The GMA SDK returns this domain for domain() method invocations when - /// converting error information from legacy mediation adapter callbacks. - static const char* const kUndefinedDomain; - - private: - friend class AdapterResponseInfo; - friend class GmaInternal; - friend class AdView; - friend class InterstitialAd; - - /// Constructor used when building results in Ad event callbacks. - explicit AdError(const AdErrorInternal& ad_error_internal); - - // Collection of response from adapters if this Result is due to a loadAd - // operation. - ResponseInfo* response_info_; - - // An internal, platform-specific implementation object that this class uses - // to interact with the Google Mobile Ads SDKs for iOS and Android. - AdErrorInternal* internal_; -}; - -/// Information about an ad response. -class ResponseInfo { - public: - /// Constructor creates an uninitialized ResponseInfo. - ResponseInfo(); - - /// Gets the AdapterResponseInfo objects for the ad response. - /// - /// @return a vector of AdapterResponseInfo objects containing metadata for - /// each adapter included in the ad response. - const std::vector& adapter_responses() const { - return adapter_responses_; - } - - /// A class name that identifies the ad network that returned the ad. - /// Returns an empty string if the ad failed to load. - const std::string& mediation_adapter_class_name() const { - return mediation_adapter_class_name_; - } - - /// Gets the response ID string for the loaded ad. Returns an empty - /// string if the ad fails to load. - const std::string& response_id() const { return response_id_; } - - /// Gets a log friendly string version of this object. - const std::string& ToString() const { return to_string_; } - - private: - friend class AdError; - friend class GmaInternal; - - explicit ResponseInfo(const ResponseInfoInternal& internal); - - std::vector adapter_responses_; - std::string mediation_adapter_class_name_; - std::string response_id_; - std::string to_string_; -}; - -/// Information about the result of an ad operation. -class AdResult { - public: - /// Default Constructor. - AdResult(); - - /// Constructor. - explicit AdResult(const AdError& ad_error); - - /// Destructor. - virtual ~AdResult(); - - /// Returns true if the operation was successful. - bool is_successful() const; - - /// An object representing an error which occurred during an ad operation. - /// If the @ref AdResult::is_successful() returned true, then the - /// @ref AdError object returned via this method will contain no contextual - /// information. - const AdError& ad_error() const; - - /// For debugging and logging purposes, successfully loaded ads provide a - /// ResponseInfo object which contains information about the adapter which - /// loaded the ad. If the ad failed to load then the object returned from - /// this method will have default values. Information about the error - /// should be retrieved via @ref AdResult::ad_error() instead. - const ResponseInfo& response_info() const; - - private: - friend class GmaInternal; - - /// Constructor invoked upon successful ad load. This contains response - /// information from the adapter which loaded the ad. - explicit AdResult(const ResponseInfo& response_info); - - /// Denotes if the @ref AdResult represents a success or an error. - bool is_successful_; - - /// Information about the error. Will be a default-constructed @ref AdError - /// if this result represents a success. - AdError ad_error_; - - /// Information from the adapter which loaded the ad. - ResponseInfo response_info_; -}; - -/// A snapshot of a mediation adapter's initialization status. -class AdapterStatus { - public: - AdapterStatus() : is_initialized_(false), latency_(0) {} - - /// Detailed description of the status. - /// - /// This method should only be used for informational purposes, such as - /// logging. Use @ref is_initialized to make logical decisions regarding an - /// adapter's status. - const std::string& description() const { return description_; } - - /// Returns the adapter's initialization state. - bool is_initialized() const { return is_initialized_; } - - /// The adapter's initialization latency in milliseconds. - /// 0 if initialization has not yet ended. - int latency() const { return latency_; } - -#if !defined(DOXYGEN) - // Equality operator for testing. - bool operator==(const AdapterStatus& rhs) const { - return (description() == rhs.description() && - is_initialized() == rhs.is_initialized() && - latency() == rhs.latency()); - } -#endif // !defined(DOXYGEN) - - private: - friend class GmaInternal; - std::string description_; - bool is_initialized_; - int latency_; -}; - -/// An immutable snapshot of the GMA SDK’s initialization status, categorized -/// by mediation adapter. -class AdapterInitializationStatus { - public: - /// Initialization status of each known ad network, keyed by its adapter's - /// class name. - std::map GetAdapterStatusMap() const { - return adapter_status_map_; - } -#if !defined(DOXYGEN) - // Equality operator for testing. - bool operator==(const AdapterInitializationStatus& rhs) const { - return (GetAdapterStatusMap() == rhs.GetAdapterStatusMap()); - } -#endif // !defined(DOXYGEN) - - private: - friend class GmaInternal; - std::map adapter_status_map_; -}; - -/// Listener to be invoked when the Ad Inspector has been closed. -class AdInspectorClosedListener { - public: - virtual ~AdInspectorClosedListener(); - - /// Called when the user clicked the ad. The AdResult contains the status of - /// the operation, including details of the error if one occurred. - virtual void OnAdInspectorClosed(const AdResult& ad_result) = 0; -}; - -/// @brief Response information for an individual ad network contained within -/// a @ref ResponseInfo object. -class AdapterResponseInfo { - public: - /// Destructor - ~AdapterResponseInfo(); - - /// @brief Information about the result including whether an error - /// occurred, and any contextual information about that error. - /// - /// @return the error that occurred while rendering the ad. If no error - /// occurred then the AdResult's successful method will return true. - AdResult ad_result() const { return ad_result_; } - - /// Returns a string representation of a class name that identifies the ad - /// network adapter. - const std::string& adapter_class_name() const { return adapter_class_name_; } - - /// Amount of time the ad network spent loading an ad. - /// - /// @return number of milliseconds the network spent loading an ad. This value - /// is 0 if the network did not make a load attempt. - int64_t latency_in_millis() const { return latency_; } - - /// A log friendly string version of this object. - const std::string& ToString() const { return to_string_; } - - private: - friend class ResponseInfo; - - /// Constructs an Adapter Response Info Object. - explicit AdapterResponseInfo(const AdapterResponseInfoInternal& internal); - - AdResult ad_result_; - std::string adapter_class_name_; - int64_t latency_; - std::string to_string_; -}; - -/// The size of a banner ad. -class AdSize { - public: - /// Denotes the orientation of the AdSize. - enum Orientation { - /// AdSize should reflect the current orientation of the device. - kOrientationCurrent = 0, - - /// AdSize will be adaptively formatted in Landscape mode. - kOrientationLandscape, - - /// AdSize will be adaptively formatted in Portrait mode. - kOrientationPortrait - }; - - /// Denotes the type size object that the @ref AdSize represents. - enum Type { - /// The standard AdSize type of a set height and width. - kTypeStandard = 0, - - /// An adaptive size anchored to a portion of the screen. - kTypeAnchoredAdaptive, - - /// An adaptive size intended to be embedded in scrollable content. - kTypeInlineAdaptive, - }; - - /// Mobile Marketing Association (MMA) banner ad size (320x50 - /// density-independent pixels). - static const AdSize kBanner; - - /// Interactive Advertising Bureau (IAB) full banner ad size - /// (468x60 density-independent pixels). - static const AdSize kFullBanner; - - /// Taller version of kBanner. Typically 320x100. - static const AdSize kLargeBanner; - - /// Interactive Advertising Bureau (IAB) leaderboard ad size - /// (728x90 density-independent pixels). - static const AdSize kLeaderboard; - - /// Interactive Advertising Bureau (IAB) medium rectangle ad size - /// (300x250 density-independent pixels). - static const AdSize kMediumRectangle; - - /// Creates a new AdSize. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// @param[in] height The height of the ad in density-independent pixels. - AdSize(uint32_t width, uint32_t height); - - /// @brief Creates an AdSize with the given width and a Google-optimized - /// height to create a banner ad in landscape mode. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a Google-optimized height - /// to create a banner ad. The size returned will have an aspect ratio - /// similar to BANNER, suitable for anchoring near the top or bottom of - /// your app. The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetLandscapeAnchoredAdaptiveBannerAdSize(uint32_t width); - - /// @brief Creates an AdSize with the given width and a Google-optimized - /// height to create a banner ad in portrait mode. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a Google-optimized height - /// to create a banner ad. The size returned will have an aspect ratio - /// similar to BANNER, suitable for anchoring near the top or bottom - /// of your app. The exact size of the ad returned can be retrieved by - /// calling @ref AdView::ad_size once the ad has been loaded. - static AdSize GetPortraitAnchoredAdaptiveBannerAdSize(uint32_t width); - - /// @brief Creates an AdSize with the given width and a Google-optimized - /// height to create a banner ad given the current orientation. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a Google-optimized height - /// to create a banner ad. The size returned will have an aspect ratio - /// similar to AdSize, suitable for anchoring near the top or bottom of - /// your app. The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetCurrentOrientationAnchoredAdaptiveBannerAdSize( - uint32_t width); - - /// @brief This ad size is most suitable for banner ads given a maximum - /// height. - /// - /// This AdSize allows Google servers to choose an optimal ad size with - /// a height less than or equal to the max height given in - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// @param[in] max_height The maximum height that a loaded ad will have. Must - /// be - /// at least 32 dp, but a maxHeight of 50 dp or higher is recommended. - /// - /// @return an AdSize with the given width and a height that is always 0. - /// The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetInlineAdaptiveBannerAdSize(int width, int max_height); - - /// @brief Creates an AdSize with the given width and the device’s - /// landscape height. - /// - /// This ad size allows Google servers to choose an optimal ad size with - /// a height less than or equal to the height of the screen in landscape - /// orientation. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a height that is always 0. - /// The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetLandscapeInlineAdaptiveBannerAdSize(int width); - - /// @brief Creates an AdSize with the given width and the device’s - /// portrait height. - /// - /// This ad size allows Google servers to choose an optimal ad size with - /// a height less than or equal to the height of the screen in portrait - /// orientation. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a height that is always 0. - /// The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetPortraitInlineAdaptiveBannerAdSize(int width); - - /// @brief A convenience method to return an inline adaptive banner ad size - /// given the current interface orientation. - /// - /// This AdSize allows Google servers to choose an optimal ad size with a - /// height less than or equal to the height of the screen in the requested - /// orientation. - /// - /// @param[in] width The width of the ad in density-independent pixels. - /// - /// @return an AdSize with the given width and a height that is always 0. - /// The exact size of the ad returned can be retrieved by calling - /// @ref AdView::ad_size once the ad has been loaded. - static AdSize GetCurrentOrientationInlineAdaptiveBannerAdSize(int width); - - /// Comparison operator. - /// - /// @return true if `rhs` refers to the same AdSize as `this`. - bool operator==(const AdSize& rhs) const; - - /// Comparison operator. - /// - /// @return true if `rhs` refers to a different AdSize as `this`. - bool operator!=(const AdSize& rhs) const; - - /// The width of the region represented by this AdSize. Value is in - /// density-independent pixels. - uint32_t width() const { return width_; } - - /// The height of the region represented by this AdSize. Value is in - /// density-independent pixels. - uint32_t height() const { return height_; } - - /// The AdSize orientation. - Orientation orientation() const { return orientation_; } - - /// The AdSize type, either standard size or adaptive. - Type type() const { return type_; } - - private: - friend class firebase::gma::internal::AdViewInternal; - - /// Returns an Anchor Adaptive AdSize Object given a width and orientation. - static AdSize GetAnchoredAdaptiveBannerAdSize(uint32_t width, - Orientation orientation); - - /// Returns true if the AdSize parameter is equivalient to this AdSize object. - bool is_equal(const AdSize& ad_size) const; - - /// Denotes the orientation for anchored adaptive AdSize objects. - Orientation orientation_; - - /// Advertisement width in platform-indepenent pixels. - uint32_t width_; - - /// Advertisement width in platform-indepenent pixels. - uint32_t height_; - - /// The type of AdSize (standard or adaptive) - Type type_; -}; - -/// Contains targeting information used to fetch an ad. -class AdRequest { - public: - /// Creates an @ref AdRequest with no custom configuration. - AdRequest(); - - /// Creates an @ref AdRequest with the optional content URL. - /// - /// When requesting an ad, apps may pass the URL of the content they are - /// serving. This enables keyword targeting to match the ad with the content. - /// - /// The URL is ignored if null or the number of characters exceeds 512. - /// - /// @param[in] content_url the url of the content being viewed. - explicit AdRequest(const char* content_url); - - ~AdRequest(); - - /// The content URL targeting information. - /// - /// @return the content URL for the @ref AdRequest. The string will be empty - /// if no content URL has been configured. - const std::string& content_url() const { return content_url_; } - - /// A Map of adapter class names to their collection of extra parameters, as - /// configured via @ref add_extra. - const std::map >& extras() - const { - return extras_; - } - - /// Keywords which will help GMA to provide targeted ads, as added by - /// @ref add_keyword. - const std::unordered_set& keywords() const { return keywords_; } - - /// Returns the set of neighboring content URLs or an empty set if no URLs - /// were set via @ref add_neighboring_content_urls(). - const std::unordered_set& neighboring_content_urls() const { - return neighboring_content_urls_; - } - - /// Add a network extra for the associated ad mediation adapter. - /// - /// Appends an extra to the corresponding list of extras for the ad mediation - /// adapter. Each ad mediation adapter can have multiple extra strings. - /// - /// @param[in] adapter_class_name the class name of the ad mediation adapter - /// for which to add the extra. - /// @param[in] extra_key a key which will be passed to the corresponding ad - /// mediation adapter. - /// @param[in] extra_value the value associated with extra_key. - void add_extra(const char* adapter_class_name, const char* extra_key, - const char* extra_value); - - /// Adds a keyword for targeting purposes. - /// - /// Multiple keywords may be added via repeated invocations of this method. - /// - /// @param[in] keyword a string that GMA will use to aid in targeting ads. - void add_keyword(const char* keyword); - - /// When requesting an ad, apps may pass the URL of the content they are - /// serving. This enables keyword targeting to match the ad with the content. - /// - /// The URL is ignored if null or the number of characters exceeds 512. - /// - /// @param[in] content_url the url of the content being viewed. - void set_content_url(const char* content_url); - - /// Adds to the list of URLs which represent web content near an ad. - /// - /// Promotes brand safety and allows displayed ads to have an app level - /// rating (MA, T, PG, etc) that is more appropriate to neighboring content. - /// - /// Subsequent invocations append to the existing list. - /// - /// @param[in] neighboring_content_urls neighboring content URLs to be - /// attached to the existing neighboring content URLs. - void add_neighboring_content_urls( - const std::vector& neighboring_content_urls); - - private: - std::string content_url_; - std::map > extras_; - std::unordered_set keywords_; - std::unordered_set neighboring_content_urls_; -}; - -/// Describes a reward credited to a user for interacting with a RewardedAd. -class AdReward { - public: - /// Creates an @ref AdReward. - AdReward(const std::string& type, int64_t amount) - : amount_(amount), type_(type) {} - - /// Returns the reward amount. - int64_t amount() const { return amount_; } - - /// Returns the type of the reward. - const std::string& type() const { return type_; } - - private: - const int64_t amount_; - const std::string type_; -}; - -/// The monetary value earned from an ad. -class AdValue { - public: - /// Allowed constants for @ref precision_type(). - enum PrecisionType { - /// An ad value with unknown precision. - kAdValuePrecisionUnknown = 0, - /// An ad value estimated from aggregated data. - kAdValuePrecisionEstimated, - /// A publisher-provided ad value, such as manual CPMs in a mediation group. - kAdValuePrecisionPublisherProvided = 2, - /// The precise value paid for this ad. - kAdValuePrecisionPrecise = 3 - }; - - /// Constructor - AdValue(const char* currency_code, PrecisionType precision_type, - int64_t value_micros) - : currency_code_(currency_code), - precision_type_(precision_type), - value_micros_(value_micros) {} - - /// The value's ISO 4217 currency code. - const std::string& currency_code() const { return currency_code_; } - - /// The precision of the reported ad value. - PrecisionType precision_type() const { return precision_type_; } - - /// The ad's value in micro-units, where 1,000,000 micro-units equal one - /// unit of the currency. - int64_t value_micros() const { return value_micros_; } - - private: - const std::string currency_code_; - const PrecisionType precision_type_; - const int64_t value_micros_; -}; - -/// @brief Listener to be invoked when ads show and dismiss full screen content, -/// such as a fullscreen ad experience or an in-app browser. -class FullScreenContentListener { - public: - virtual ~FullScreenContentListener(); - - /// Called when the user clicked the ad. - virtual void OnAdClicked() {} - - /// Called when the ad dismissed full screen content. - virtual void OnAdDismissedFullScreenContent() {} - - /// Called when the ad failed to show full screen content. - /// - /// @param[in] ad_error An object containing detailed information - /// about the error. - virtual void OnAdFailedToShowFullScreenContent(const AdError& ad_error) {} - - /// Called when an impression is recorded for an ad. - virtual void OnAdImpression() {} - - /// Called when the ad showed the full screen content. - virtual void OnAdShowedFullScreenContent() {} -}; - -/// Listener to be invoked when ads have been estimated to earn money. -class PaidEventListener { - public: - virtual ~PaidEventListener(); - - /// Called when an ad is estimated to have earned money. - virtual void OnPaidEvent(const AdValue& value) {} -}; - -/// @brief Global configuration that will be used for every @ref AdRequest. -/// Set the configuration via @ref SetRequestConfiguration. -struct RequestConfiguration { - /// A maximum ad content rating, which may be configured via - /// @ref max_ad_content_rating. - enum MaxAdContentRating { - /// No content rating has been specified. - kMaxAdContentRatingUnspecified = -1, - - /// Content suitable for general audiences, including families. - kMaxAdContentRatingG, - - /// Content suitable only for mature audiences. - kMaxAdContentRatingMA, - - /// Content suitable for most audiences with parental guidance. - kMaxAdContentRatingPG, - - /// Content suitable for teen and older audiences. - kMaxAdContentRatingT - }; - - /// Specify whether you would like your app to be treated as child-directed - /// for purposes of the Children’s Online Privacy Protection Act (COPPA). - /// Values defined here may be configured via - /// @ref tag_for_child_directed_treatment. - enum TagForChildDirectedTreatment { - /// Indicates that ad requests will include no indication of how you would - /// like your app treated with respect to COPPA. - kChildDirectedTreatmentUnspecified = -1, - - /// Indicates that your app should not be treated as child-directed for - /// purposes of the Children’s Online Privacy Protection Act (COPPA). - kChildDirectedTreatmentFalse, - - /// Indicates that your app should be treated as child-directed for purposes - /// of the Children’s Online Privacy Protection Act (COPPA). - kChildDirectedTreatmentTrue - }; - - /// Configuration values to mark your app to receive treatment for users in - /// the European Economic Area (EEA) under the age of consent. Values defined - /// here should be configured via @ref tag_for_under_age_of_consent. - enum TagForUnderAgeOfConsent { - /// Indicates that the publisher has not specified whether the ad request - /// should receive treatment for users in the European Economic Area (EEA) - /// under the age of consent. - kUnderAgeOfConsentUnspecified = -1, - - /// Indicates the publisher specified that the ad request should not receive - /// treatment for users in the European Economic Area (EEA) under the age of - /// consent. - kUnderAgeOfConsentFalse, - - /// Indicates the publisher specified that the ad request should receive - /// treatment for users in the European Economic Area (EEA) under the age of - /// consent. - kUnderAgeOfConsentTrue - }; - - /// Sets a maximum ad content rating. GMA ads returned for your app will - /// have a content rating at or below that level. - MaxAdContentRating max_ad_content_rating; - - /// @brief Allows you to specify whether you would like your app - /// to be treated as child-directed for purposes of the Children’s Online - /// Privacy Protection Act (COPPA) - - /// http://business.ftc.gov/privacy-and-security/childrens-privacy. - /// - /// If you set this value to - /// RequestConfiguration.kChildDirectedTreatmentTrue, you will indicate - /// that your app should be treated as child-directed for purposes of the - /// Children’s Online Privacy Protection Act (COPPA). - /// - /// If you set this value to - /// RequestConfiguration.kChildDirectedTreatmentFalse, you will indicate - /// that your app should not be treated as child-directed for purposes of the - /// Children’s Online Privacy Protection Act (COPPA). - /// - /// If you do not set this value, or set this value to - /// RequestConfiguration.kChildDirectedTreatmentUnspecified, ad requests will - /// include no indication of how you would like your app treated with respect - /// to COPPA. - /// - /// By setting this value, you certify that this notification is accurate and - /// you are authorized to act on behalf of the owner of the app. You - /// understand that abuse of this setting may result in termination of your - /// Google account. - /// - /// @note: it may take some time for this designation to be fully implemented - /// in applicable Google services. - /// - TagForChildDirectedTreatment tag_for_child_directed_treatment; - - /// This value allows you to mark your app to receive treatment for users in - /// the European Economic Area (EEA) under the age of consent. This feature is - /// designed to help facilitate compliance with the General Data Protection - /// Regulation (GDPR). Note that you may have other legal obligations under - /// GDPR. Please review the European Union's guidance and consult with your - /// own legal counsel. Please remember that Google's tools are designed to - /// facilitate compliance and do not relieve any particular publisher of its - /// obligations under the law. - /// - /// When using this feature, a Tag For Users under the Age of Consent in - /// Europe (TFUA) parameter will be included in all ad requests. This - /// parameter disables personalized advertising, including remarketing, for - /// that specific ad request. It also disables requests to third-party ad - /// vendors, such as ad measurement pixels and third-party ad servers. - /// - /// If you set this value to RequestConfiguration.kUnderAgeOfConsentTrue, you - /// will indicate that you want your app to be handled in a manner suitable - /// for users under the age of consent. - /// - /// If you set this value to RequestConfiguration.kUnderAgeOfConsentFalse, - /// you will indicate that you don't want your app to be handled in a manner - /// suitable for users under the age of consent. - /// - /// If you do not set this value, or set this value to - /// kUnderAgeOfConsentUnspecified, your app will include no indication of how - /// you would like your app to be handled in a manner suitable for users under - /// the age of consent. - TagForUnderAgeOfConsent tag_for_under_age_of_consent; - - /// Sets a list of test device IDs corresponding to test devices which will - /// always request test ads. - std::vector test_device_ids; -}; - -/// Listener to be invoked when the user earned a reward. -class UserEarnedRewardListener { - public: - virtual ~UserEarnedRewardListener(); - /// Called when the user earned a reward. The app is responsible for - /// crediting the user with the reward. - /// - /// @param[in] reward the @ref AdReward that should be granted to the user. - virtual void OnUserEarnedReward(const AdReward& reward) {} -}; - -} // namespace gma -} // namespace firebase - -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_TYPES_H_ diff --git a/ump/src/include/firebase/gma/ump.h b/ump/src/include/firebase/ump.h similarity index 100% rename from ump/src/include/firebase/gma/ump.h rename to ump/src/include/firebase/ump.h diff --git a/ump/src/include/firebase/gma/ump/consent_info.h b/ump/src/include/firebase/ump/consent_info.h similarity index 100% rename from ump/src/include/firebase/gma/ump/consent_info.h rename to ump/src/include/firebase/ump/consent_info.h diff --git a/ump/src/include/firebase/gma/ump/types.h b/ump/src/include/firebase/ump/types.h similarity index 100% rename from ump/src/include/firebase/gma/ump/types.h rename to ump/src/include/firebase/ump/types.h diff --git a/ump/src/ios/ump/consent_info_internal_ios.h b/ump/src/ios/consent_info_internal_ios.h similarity index 100% rename from ump/src/ios/ump/consent_info_internal_ios.h rename to ump/src/ios/consent_info_internal_ios.h diff --git a/ump/src/ios/ump/consent_info_internal_ios.mm b/ump/src/ios/consent_info_internal_ios.mm similarity index 100% rename from ump/src/ios/ump/consent_info_internal_ios.mm rename to ump/src/ios/consent_info_internal_ios.mm diff --git a/ump/src/stub/ump/consent_info_internal_stub.cc b/ump/src/stub/consent_info_internal_stub.cc similarity index 100% rename from ump/src/stub/ump/consent_info_internal_stub.cc rename to ump/src/stub/consent_info_internal_stub.cc diff --git a/ump/src/stub/ump/consent_info_internal_stub.h b/ump/src/stub/consent_info_internal_stub.h similarity index 100% rename from ump/src/stub/ump/consent_info_internal_stub.h rename to ump/src/stub/consent_info_internal_stub.h From 7ca910fcf444f43ec97cee24ad8eeaa8e743dfa6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:36:02 -0700 Subject: [PATCH 03/27] Rename and refactor all of the source files and namespaces. --- .../android/consent_info_internal_android.cc | 39 +- .../android/consent_info_internal_android.h | 35 +- ump/src/common/consent_info.cc | 8 +- ump/src/common/consent_info_internal.cc | 4 +- ump/src/common/consent_info_internal.h | 12 +- ump/src/include/firebase/ump.h | 10 +- ump/src/include/firebase/ump/consent_info.h | 14 +- ump/src/include/firebase/ump/types.h | 6 +- ump/src/ios/consent_info_internal_ios.h | 10 +- ump/src/ios/consent_info_internal_ios.mm | 14 +- ump/src/stub/consent_info_internal_stub.cc | 4 +- ump/src/stub/consent_info_internal_stub.h | 10 +- .../gma/internal/cpp/AdInspectorHelper.java | 49 -- .../gma/internal/cpp/AdRequestHelper.java | 48 -- .../gma/internal/cpp/AdViewHelper.java | 658 ------------------ .../gma/internal/cpp/ConstantsHelper.java | 105 --- .../gma/internal/cpp/DownloadHelper.java | 198 ------ .../internal/cpp/GmaInitializationHelper.java | 37 - .../internal/cpp/InterstitialAdHelper.java | 319 --------- .../gma/internal/cpp/NativeAdHelper.java | 338 --------- .../gma/internal/cpp/QueryInfoHelper.java | 218 ------ .../gma/internal/cpp/RewardedAdHelper.java | 345 --------- .../internal/cpp/ConsentInfoHelper.java | 4 +- 23 files changed, 86 insertions(+), 2399 deletions(-) delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java delete mode 100644 ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java rename ump/src_java/com/google/firebase/{gma => ump}/internal/cpp/ConsentInfoHelper.java (98%) diff --git a/ump/src/android/consent_info_internal_android.cc b/ump/src/android/consent_info_internal_android.cc index 903b7126cd..defb784952 100644 --- a/ump/src/android/consent_info_internal_android.cc +++ b/ump/src/android/consent_info_internal_android.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "gma/src/android/ump/consent_info_internal_android.h" +#include "ump/src/android/consent_info_internal_android.h" #include @@ -25,17 +25,13 @@ #include "app/src/thread.h" #include "app/src/util_android.h" #include "firebase/internal/common.h" -#include "gma/gma_resources.h" -#include "gma/src/android/gma_android.h" +#include "ump/ump_resources.h" +#include "ump/src/android/ump_android.h" namespace firebase { -namespace gma { namespace ump { namespace internal { -ConsentInfoInternalAndroid* ConsentInfoInternalAndroid::s_instance = nullptr; -firebase::Mutex ConsentInfoInternalAndroid::s_instance_mutex; - // clang-format off #define CONSENTINFOHELPER_METHODS(X) \ X(Constructor, "", "(JLandroid/app/Activity;)V"), \ @@ -83,7 +79,7 @@ METHOD_LOOKUP_DECLARATION(consent_info_helper, CONSENTINFOHELPER_METHODS, METHOD_LOOKUP_DEFINITION( consent_info_helper, - "com/google/firebase/gma/internal/cpp/ConsentInfoHelper", + "com/google/firebase/ump/internal/cpp/ConsentInfoHelper", CONSENTINFOHELPER_METHODS, CONSENTINFOHELPER_FIELDS); // clang-format off @@ -263,23 +259,23 @@ ConsentInfoInternalAndroid::ConsentInfoInternalAndroid(JNIEnv* env, util::Initialize(env, activity); env->GetJavaVM(&java_vm_); - // Between this and GMA, we only want to load these files once. + // Ensure we only load these files once. { MutexLock lock( - ::firebase::gma::internal::g_cached_gma_embedded_files_mutex); - if (::firebase::gma::internal::g_cached_gma_embedded_files == nullptr) { - ::firebase::gma::internal::g_cached_gma_embedded_files = + ::firebase::ump::internal::g_cached_ump_embedded_files_mutex); + if (::firebase::ump::internal::g_cached_ump_embedded_files == nullptr) { + ::firebase::ump::internal::g_cached_ump_embedded_files = new std::vector(); - *::firebase::gma::internal::g_cached_gma_embedded_files = + *::firebase::ump::internal::g_cached_ump_embedded_files = util::CacheEmbeddedFiles(env, activity, firebase::internal::EmbeddedFile::ToVector( - firebase_gma::gma_resources_filename, - firebase_gma::gma_resources_data, - firebase_gma::gma_resources_size)); + firebase_ump::ump_resources_filename, + firebase_ump::ump_resources_data, + firebase_ump::ump_resources_size)); } } const std::vector& embedded_files = - *::firebase::gma::internal::g_cached_gma_embedded_files; + *::firebase::ump::internal::g_cached_ump_embedded_files; if (!(consent_info_helper::CacheClassFromFiles(env, activity, &embedded_files) != nullptr && @@ -335,7 +331,7 @@ ConsentStatus ConsentInfoInternalAndroid::CppConsentStatusFromAndroid( if (status == enums().consentstatus_not_required) return kConsentStatusNotRequired; if (status == enums().consentstatus_obtained) return kConsentStatusObtained; - LogWarning("GMA: Unknown ConsentStatus returned by UMP Android SDK: %d", + LogWarning("UMP: Unknown ConsentStatus returned by UMP Android SDK: %d", (int)status); return kConsentStatusUnknown; } @@ -350,7 +346,7 @@ ConsentInfoInternalAndroid::CppPrivacyOptionsRequirementStatusFromAndroid( if (status == enums().privacy_options_requirement_not_required) return kPrivacyOptionsRequirementStatusNotRequired; LogWarning( - "GMA: Unknown PrivacyOptionsRequirementStatus returned by UMP Android " + "UMP: Unknown PrivacyOptionsRequirementStatus returned by UMP Android " "SDK: %d", (int)status); return kPrivacyOptionsRequirementStatusUnknown; @@ -387,7 +383,7 @@ ConsentInfoInternalAndroid::CppConsentRequestErrorFromAndroidFormError( else return kConsentRequestErrorInvalidOperation; } - LogWarning("GMA: Unknown RequestError returned by UMP Android SDK: %d (%s)", + LogWarning("UMP: Unknown RequestError returned by UMP Android SDK: %d (%s)", (int)error, message ? message : ""); return kConsentRequestErrorUnknown; } @@ -410,7 +406,7 @@ ConsentInfoInternalAndroid::CppConsentFormErrorFromAndroidFormError( else return kConsentFormErrorInvalidOperation; } - LogWarning("GMA: Unknown RequestError returned by UMP Android SDK: %d (%s)", + LogWarning("UMP: Unknown RequestError returned by UMP Android SDK: %d (%s)", (int)error, message ? message : ""); return kConsentFormErrorUnknown; } @@ -664,5 +660,4 @@ void ConsentInfoInternalAndroid::CompleteFutureFromJniCallback( } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase diff --git a/ump/src/android/consent_info_internal_android.h b/ump/src/android/consent_info_internal_android.h index 4c0498671f..f5ab3b8916 100644 --- a/ump/src/android/consent_info_internal_android.h +++ b/ump/src/android/consent_info_internal_android.h @@ -14,19 +14,43 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ -#define FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ +#ifndef FIREBASE_UMP_SRC_ANDROID_CONSENT_INFO_INTERNAL_ANDROID_H_ +#define FIREBASE_UMP_SRC_ANDROID_CONSENT_INFO_INTERNAL_ANDROID_H_ #include #include "app/src/util_android.h" #include "firebase/internal/mutex.h" -#include "gma/src/common/ump/consent_info_internal.h" +#include "ump/src/common/consent_info_internal.h" namespace firebase { -namespace gma { namespace ump { + +// clang-format off +#define UMP_INITIALIZATION_HELPER_METHODS(X) \ + X(InitializeUmp, "initializeUmp", "(Landroid/content/Context;)V", \ + util::kMethodTypeStatic) +// clang-format on + +METHOD_LOOKUP_DECLARATION(ump_initialization_helper, + UMP_INITIALIZATION_HELPER_METHODS); + +// Needed when UMP is initialized without Firebase. +JNIEnv* GetJNI(); + +// Retrieves the activity used to initialize UMP. +jobject GetActivity(); + +// Register the native callbacks needed by the Futures. +bool RegisterNatives(); + +// Release classes registered by this module. +void ReleaseClasses(JNIEnv* env); + namespace internal { +extern ::firebase::Mutex g_cached_ump_embedded_files_mutex; +extern std::vector<::firebase::internal::EmbeddedFile>* + g_cached_ump_embedded_files; class ConsentInfoInternalAndroid : public ConsentInfoInternal { public: @@ -126,7 +150,6 @@ class ConsentInfoInternalAndroid : public ConsentInfoInternal { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_ANDROID_UMP_CONSENT_INFO_INTERNAL_ANDROID_H_ +#endif // FIREBASE_UMP_SRC_ANDROID_CONSENT_INFO_INTERNAL_ANDROID_H_ diff --git a/ump/src/common/consent_info.cc b/ump/src/common/consent_info.cc index 334a11d735..3b008c11bf 100644 --- a/ump/src/common/consent_info.cc +++ b/ump/src/common/consent_info.cc @@ -14,16 +14,15 @@ * limitations under the License. */ -#include "firebase/gma/ump/consent_info.h" +#include "firebase/ump/consent_info.h" #include "app/src/assert.h" #include "firebase/app.h" -#include "firebase/gma/ump.h" +#include "firebase/ump.h" #include "firebase/internal/platform.h" -#include "gma/src/common/ump/consent_info_internal.h" +#include "ump//src/common/consent_info_internal.h" namespace firebase { -namespace gma { namespace ump { ConsentInfo* ConsentInfo::s_instance_ = nullptr; @@ -180,5 +179,4 @@ void ConsentInfo::Reset() { } } // namespace ump -} // namespace gma } // namespace firebase diff --git a/ump/src/common/consent_info_internal.cc b/ump/src/common/consent_info_internal.cc index 3a3e9cd4b8..ca6f4677f6 100644 --- a/ump/src/common/consent_info_internal.cc +++ b/ump/src/common/consent_info_internal.cc @@ -14,12 +14,11 @@ * limitations under the License. */ -#include "gma/src/common/ump/consent_info_internal.h" +#include "ump/src/common/consent_info_internal.h" #include "app/src/include/firebase/internal/platform.h" namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -86,5 +85,4 @@ const char* ConsentInfoInternal::GetConsentFormErrorMessage( } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase diff --git a/ump/src/common/consent_info_internal.h b/ump/src/common/consent_info_internal.h index 94d87a5ec0..f4cc530dda 100644 --- a/ump/src/common/consent_info_internal.h +++ b/ump/src/common/consent_info_internal.h @@ -14,14 +14,14 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ -#define FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ +#ifndef FIREBASE_UMP_SRC_COMMON_CONSENT_INFO_INTERNAL_H_ +#define FIREBASE_UMP_SRC_COMMON_CONSENT_INFO_INTERNAL_H_ #include "app/src/cleanup_notifier.h" #include "app/src/reference_counted_future_impl.h" #include "firebase/future.h" -#include "firebase/gma/ump.h" -#include "firebase/gma/ump/types.h" +#include "firebase/ump.h" +#include "firebase/ump/types.h" #include "firebase/internal/platform.h" #if FIREBASE_PLATFORM_ANDROID @@ -29,7 +29,6 @@ #endif namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -136,7 +135,6 @@ class ConsentInfoInternal { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_COMMON_UMP_CONSENT_INFO_INTERNAL_H_ +#endif // FIREBASE_UMP_SRC_COMMON_CONSENT_INFO_INTERNAL_H_ diff --git a/ump/src/include/firebase/ump.h b/ump/src/include/firebase/ump.h index 26e90d630b..bd33f1f238 100644 --- a/ump/src/include/firebase/ump.h +++ b/ump/src/include/firebase/ump.h @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ +#ifndef FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_H_ +#define FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_H_ -#include "firebase/gma/ump/consent_info.h" -#include "firebase/gma/ump/types.h" +#include "firebase/ump/consent_info.h" +#include "firebase/ump/types.h" -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_H_ +#endif // FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_H_ diff --git a/ump/src/include/firebase/ump/consent_info.h b/ump/src/include/firebase/ump/consent_info.h index 8f69918ed0..40d71d431f 100644 --- a/ump/src/include/firebase/ump/consent_info.h +++ b/ump/src/include/firebase/ump/consent_info.h @@ -14,12 +14,12 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ +#ifndef FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_CONSENT_INFO_H_ +#define FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_CONSENT_INFO_H_ #include "firebase/app.h" #include "firebase/future.h" -#include "firebase/gma/ump/types.h" +#include "firebase/ump/types.h" #include "firebase/internal/platform.h" #if FIREBASE_PLATFORM_ANDROID @@ -27,7 +27,6 @@ #endif // FIREBASE_PLATFORM_ANDROID namespace firebase { -namespace gma { /// @brief API for User Messaging Platform. /// /// The User Messaging Platform (UMP) SDK is Google’s option to handle user @@ -75,10 +74,10 @@ class ConsentInfo { /// do something like this: /// @code /// #if defined(__ANDROID__) - /// consent_info = firebase::gma::ump::ConsentInfo::GetInstance(jni_env, + /// consent_info = firebase::ump::ConsentInfo::GetInstance(jni_env, /// activity); /// #else - /// consent_info = firebase::gma::ump::GetInstance(); + /// consent_info = firebase::ump::GetInstance(); /// #endif /// @endcode /// @@ -243,7 +242,6 @@ class ConsentInfo { }; } // namespace ump -} // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_CONSENT_INFO_H_ +#endif // FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_CONSENT_INFO_H_ diff --git a/ump/src/include/firebase/ump/types.h b/ump/src/include/firebase/ump/types.h index 0684858582..09329326d2 100644 --- a/ump/src/include/firebase/ump/types.h +++ b/ump/src/include/firebase/ump/types.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ -#define FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ +#ifndef FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_TYPES_H_ +#define FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_TYPES_H_ #include #include @@ -176,4 +176,4 @@ enum PrivacyOptionsRequirementStatus { } // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_INCLUDE_FIREBASE_GMA_UMP_TYPES_H_ +#endif // FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_TYPES_H_ diff --git a/ump/src/ios/consent_info_internal_ios.h b/ump/src/ios/consent_info_internal_ios.h index 6e860edb40..f1db540258 100644 --- a/ump/src/ios/consent_info_internal_ios.h +++ b/ump/src/ios/consent_info_internal_ios.h @@ -14,16 +14,15 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ -#define FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ +#ifndef FIREBASE_UMP_SRC_IOS_CONSENT_INFO_INTERNAL_IOS_H_ +#define FIREBASE_UMP_SRC_IOS_CONSENT_INFO_INTERNAL_IOS_H_ #include #include "firebase/internal/mutex.h" -#include "gma/src/common/ump/consent_info_internal.h" +#include "ump/src/common/consent_info_internal.h" namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -63,7 +62,6 @@ class ConsentInfoInternalIos : public ConsentInfoInternal { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_IOS_UMP_CONSENT_INFO_INTERNAL_IOS_H_ +#endif // FIREBASE_UMP_SRC_IOS_CONSENT_INFO_INTERNAL_IOS_H_ diff --git a/ump/src/ios/consent_info_internal_ios.mm b/ump/src/ios/consent_info_internal_ios.mm index c4ea199eca..9bb15f1f1a 100644 --- a/ump/src/ios/consent_info_internal_ios.mm +++ b/ump/src/ios/consent_info_internal_ios.mm @@ -14,14 +14,13 @@ * limitations under the License. */ -#include "gma/src/ios/ump/consent_info_internal_ios.h" +#include "ump/src/ios/consent_info_internal_ios.h" #include "app/src/assert.h" #include "app/src/thread.h" #include "app/src/util_ios.h" namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -58,7 +57,7 @@ static ConsentRequestError CppRequestErrorFromIosRequestError(NSInteger code) { case UMPRequestErrorCodeNetwork: return kConsentRequestErrorNetwork; default: - LogWarning("GMA: Unknown UMPRequestErrorCode returned by UMP iOS SDK: %d", (int)code); + LogWarning("UMP: Unknown UMPRequestErrorCode returned by UMP iOS SDK: %d", (int)code); return kConsentRequestErrorUnknown; } } @@ -76,7 +75,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { case UMPFormErrorCodeInvalidViewController: return kConsentFormErrorInvalidOperation; default: - LogWarning("GMA: Unknown UMPFormErrorCode returned by UMP iOS SDK: %d", (int)code); + LogWarning("UMP: Unknown UMPFormErrorCode returned by UMP iOS SDK: %d", (int)code); return kConsentFormErrorUnknown; } } @@ -161,7 +160,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { case UMPConsentStatusUnknown: return kConsentStatusUnknown; default: - LogWarning("GMA: Unknown UMPConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); + LogWarning("UMP: Unknown UMPConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); return kConsentStatusUnknown; } } @@ -176,7 +175,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { case UMPFormStatusUnknown: return kConsentFormStatusUnknown; default: - LogWarning("GMA: Unknown UMPFormConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); + LogWarning("UMP: Unknown UMPFormConsentStatus returned by UMP iOS SDK: %d", (int)ios_status); return kConsentFormStatusUnknown; } } @@ -313,7 +312,7 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { case UMPPrivacyOptionsRequirementStatusUnknown: return kPrivacyOptionsRequirementStatusUnknown; default: - LogWarning("GMA: Unknown UMPPrivacyOptionsRequirementStatus returned by UMP iOS SDK: %d", + LogWarning("UMP: Unknown UMPPrivacyOptionsRequirementStatus returned by UMP iOS SDK: %d", (int)ios_status); return kPrivacyOptionsRequirementStatusUnknown; } @@ -365,5 +364,4 @@ static ConsentFormError CppFormErrorFromIosFormError(NSInteger code) { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase diff --git a/ump/src/stub/consent_info_internal_stub.cc b/ump/src/stub/consent_info_internal_stub.cc index 6bc92ec08c..a0a4c4f4da 100644 --- a/ump/src/stub/consent_info_internal_stub.cc +++ b/ump/src/stub/consent_info_internal_stub.cc @@ -14,12 +14,11 @@ * limitations under the License. */ -#include "gma/src/stub/ump/consent_info_internal_stub.h" +#include "ump/src/stub/consent_info_internal_stub.h" #include "app/src/thread.h" namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -163,5 +162,4 @@ void ConsentInfoInternalStub::Reset() { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase diff --git a/ump/src/stub/consent_info_internal_stub.h b/ump/src/stub/consent_info_internal_stub.h index 83368de284..9da0655e0d 100644 --- a/ump/src/stub/consent_info_internal_stub.h +++ b/ump/src/stub/consent_info_internal_stub.h @@ -14,13 +14,12 @@ * limitations under the License. */ -#ifndef FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ -#define FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ +#ifndef FIREBASE_UMP_SRC_STUB_CONSENT_INFO_INTERNAL_STUB_H_ +#define FIREBASE_UMP_SRC_STUB_CONSENT_INFO_INTERNAL_STUB_H_ -#include "gma/src/common/ump/consent_info_internal.h" +#include "ump/src/common/consent_info_internal.h" namespace firebase { -namespace gma { namespace ump { namespace internal { @@ -80,7 +79,6 @@ class ConsentInfoInternalStub : public ConsentInfoInternal { } // namespace internal } // namespace ump -} // namespace gma } // namespace firebase -#endif // FIREBASE_GMA_SRC_STUB_UMP_CONSENT_INFO_INTERNAL_STUB_H_ +#endif // FIREBASE_SRC_STUB_CONSENT_INFO_INTERNAL_STUB_H_ diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java deleted file mode 100644 index 9d7e1add66..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/AdInspectorHelper.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2022 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.gma.internal.cpp; - -import android.util.Log; -import com.google.android.gms.ads.AdError; -import com.google.android.gms.ads.AdInspectorError; -import com.google.android.gms.ads.MobileAds; -import com.google.android.gms.ads.OnAdInspectorClosedListener; - -/** Helper class for listening to AdInspector closed events. */ -public final class AdInspectorHelper implements OnAdInspectorClosedListener { - /** - * Pointer to the C++ AdInspectorClosedListener object to invoke when the AdInspector has been - * closed. - */ - private long mNativeCallbackPtr; - - /** Constructor. */ - AdInspectorHelper(long nativeCallbackPtr) { - mNativeCallbackPtr = nativeCallbackPtr; - } - - /** Method that the Android GMA SDK invokes when the AdInspector has been closed. */ - @Override - public void onAdInspectorClosed(AdInspectorError error) { - adInspectorClosedCallback(mNativeCallbackPtr, error); - } - - /** - * Native callback to which will signal the customer's application that the AdInspector has been - * closed. A null AdError signifies success. - */ - public static native void adInspectorClosedCallback(long nativeCallbackPtr, AdError error); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java deleted file mode 100644 index a06d0a4674..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/AdRequestHelper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -import java.util.Calendar; -import java.util.Date; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java AdRequest objects cleaner. - * This involves translating calls coming from C++ into their (typically more complicated) Java - * equivalents. - */ -public class AdRequestHelper { - public AdRequestHelper() {} - - /** - * Creates a {@link java.lang.Date} from the provided date information. - * - * @param year The year to use in creating the Date object - * @param month The month to use in creating the Date object - * @param day The day to use in creating the Date object - * @return A Date object with the appropriate date - */ - public Date createDate(int year, int month, int day) { - try { - Calendar cal = Calendar.getInstance(); - cal.setLenient(false); - cal.set(year, month - 1, day); - return cal.getTime(); - } catch (IllegalArgumentException e) { - return null; - } - } -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java deleted file mode 100644 index 5c91977228..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/AdViewHelper.java +++ /dev/null @@ -1,658 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -import android.app.Activity; -import android.graphics.drawable.ColorDrawable; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.ViewTreeObserver; -import android.widget.PopupWindow; -import com.google.android.gms.ads.AdListener; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdSize; -import com.google.android.gms.ads.AdValue; -import com.google.android.gms.ads.AdView; -import com.google.android.gms.ads.LoadAdError; -import com.google.android.gms.ads.OnPaidEventListener; -import com.google.android.gms.ads.ResponseInfo; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java AdView objects cleaner. - * It's designed to wrap and adapt a single instance of AdView, translate calls coming from C++ into - * their (typically more complicated) Java equivalents, and convert the Java listener patterns into - * game engine-friendly state machine polling. - */ -public class AdViewHelper implements ViewTreeObserver.OnPreDrawListener { - // It's possible to attempt to show a popup when an activity doesn't have focus. This value - // controls the number of times the AdViewHelper object checks for activity window focus - // before timing out. Assuming 10ms per retry this value attempts to retry for 2 minutes before - // timing out. - private static final int POPUP_SHOW_RETRY_COUNT = 12000; - - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // The number of milliseconds to wait before attempting to create a PopUpWindow to hold an ad. - private static final int WEBVIEW_DELAY_MILLISECONDS = 200; - - // Pointer to the AdViewInternalAndroid object that created this object. - private long mAdViewInternalPtr; - - // The GMA SDK AdView associated with this helper. - private AdView mAdView; - - // Flag indicating whether an ad is showing in mAdView. - private boolean mAdViewContainsAd; - - // Flag indicating that the Bounding Box listener callback should be invoked - // the next time mAdView's OnPreDrawListener gets an OnPreDraw event. - private AtomicBoolean mNotifyBoundingBoxListenerOnNextDraw; - - // The {@link Activity} this helper uses to display its {@link AdView}. - private Activity mActivity; - - // The ad unit ID to use for the {@link AdView}. - private String mAdUnitId; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to LoadAd. - private long mLoadAdCallbackDataPtr; - - // Synchronization object for thread safe access to: - // * mAdViewInternalPtr - // * mLoadAdCallbackDataPtr - private final Object mAdViewLock; - - // {@link PopupWindow } that will contain the {@link AdView}. This is done to - // guarantee the ad is drawn properly even when the application uses a - // {@link android.app.NativeActivity}. - private PopupWindow mPopUp; - - // Runnable that is trigged to show the Ad {@link PopupWindow}. - // When this is set to null, the popup should not be shown. - private Runnable mPopUpRunnable; - - // Lock used to synchronize the state of the popup window. - private final Object mPopUpLock; - - // Number of times the AdViewHelper object has attempted to show the popup window before the - // activity has focus. - private int mPopUpShowRetryCount; - - // Flag indicating whether the {@link AdView} is currently intended to be - // positioned with (x,y) coordinates rather than one of the pre-defined - // positions (such as ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT). - private boolean mShouldUseXYForPosition; - - // The user's desired pre-defined position for the {@link AdView}. - private int mDesiredPosition; - - // The user's desired pre-defined X coordinate for the {@link AdView}. - private int mDesiredX; - - // The user's desired pre-defined Y coordinate for the {@link AdView}. - private int mDesiredY; - - /** - * Constructor. - */ - public AdViewHelper(long AdViewInternalPtr, AdView adView) { - mAdViewInternalPtr = AdViewInternalPtr; - mAdView = adView; - mDesiredPosition = ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT; - mShouldUseXYForPosition = false; - mAdViewContainsAd = false; - mNotifyBoundingBoxListenerOnNextDraw = new AtomicBoolean(false); - mAdViewLock = new Object(); - mPopUpLock = new Object(); - mPopUpShowRetryCount = 0; - } - - /** - * Initializes the {@link AdView}. This stores the activity for use with - * callback and load operations. - */ - - public void initialize(Activity activity) { - mActivity = activity; - } - - /** - * Destroy/deallocate the {@link PopupWindow} and {@link AdView}. - */ - public void destroy(final long callbackDataPtr, final boolean destructor_invocation) { - // If the Activity isn't initialized, or already Destroyed, then there's - // nothing to destroy. - if (mActivity != null) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - // Stop any attempts to show the popup window. - synchronized (mPopUpLock) { - mPopUpRunnable = null; - } - - if (mAdView != null) { - mAdView.setAdListener(null); - mAdView.setOnPaidEventListener(null); - mAdView.destroy(); - mAdView = null; - } - - synchronized (mPopUpLock) { - if (mPopUp != null) { - mPopUp.dismiss(); - mPopUp = null; - } - } - synchronized (mAdViewLock) { - if (destructor_invocation == false) { - notifyBoundingBoxChanged(mAdViewInternalPtr); - } - mAdViewInternalPtr = CPP_NULLPTR; - } - mActivity = null; - if (destructor_invocation) { - // AdViews's C++ destructor does not pass a future - // to callback and complete, but the reference to this object - // which should be released. - releaseAdViewGlobalReferenceCallback(callbackDataPtr); - } else { - completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - } - }); - } else { - if (callbackDataPtr != CPP_NULLPTR) { - completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - } - } - - /** - * Loads an ad for the underlying AdView object. - */ - public void loadAd(long callbackDataPtr, final AdRequest request) { - if (mActivity == null) { - return; - } - - synchronized (mAdViewLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeAdViewLoadAdInternalError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - mLoadAdCallbackDataPtr = callbackDataPtr; - } - - if (mAdView == null) { - synchronized (mAdViewLock) { - completeAdViewLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } else { - mAdView.loadAd(request); - } - } - - /** - * Hides the {@link AdView}. - */ - public void hide(final long callbackDataPtr) { - if (mActivity == null) { - return; - } - - int errorCode; - String errorMessage; - - synchronized (mPopUpLock) { - // Stop any attempts to show the popup window. - mPopUpRunnable = null; - - if (mAdView == null || mPopUp == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - mPopUp.dismiss(); - mPopUp = null; - } - } - - completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - - /** - * Shows the {@link AdView}. - */ - public void show(final long callbackDataPtr) { - if (mActivity == null) { - return; - } - updatePopUpLocation(callbackDataPtr); - } - - /** - * Pauses the {@link AdView}. - */ - public void pause(final long callbackDataPtr) { - if (mActivity == null) { - return; - } else if (mAdView != null) { - mAdView.pause(); - } - completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Resume the {@link AdView} (from a pause). - */ - public void resume(final long callbackDataPtr) { - if (mActivity == null) { - return; - } else if (mAdView != null) { - mAdView.resume(); - } - - completeAdViewFutureCallback(callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_NONE, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Moves the {@link AdView} to the provided (x,y) screen coordinates. - */ - public void moveTo(final long callbackDataPtr, int x, int y) { - if (mActivity == null) { - return; - } - - synchronized (mPopUpLock) { - mShouldUseXYForPosition = true; - mDesiredX = x; - mDesiredY = y; - if (mPopUp != null) { - updatePopUpLocation(callbackDataPtr); - } - } - } - - /** - * Moves the {@link AdView} to the provided screen position. - */ - public void moveTo(final long callbackDataPtr, final int position) { - if (mActivity == null) { - return; - } - - synchronized (mPopUpLock) { - mShouldUseXYForPosition = false; - mDesiredPosition = position; - if (mPopUp != null) { - updatePopUpLocation(callbackDataPtr); - } - } - } - - /** - * Returns an integer array consisting of the current onscreen width, height, x-coordinate, and - * y-coordinate of the {@link AdView}. These values make up the AdView's BoundingBox. - */ - public int[] getBoundingBox() { - synchronized (mPopUpLock) { - int width = -1; - int height = -1; - int x = -1; - int y = -1; - if (mPopUp != null) { - int[] onScreenLocation = new int[2]; - mPopUp.getContentView().getLocationOnScreen(onScreenLocation); - x = onScreenLocation[0]; - y = onScreenLocation[1]; - - if (mAdView != null) { - if (mPopUp.isShowing()) { - width = mAdView.getWidth(); - height = mAdView.getHeight(); - } else { - width = height = 0; - } - } - } - return new int[] {width, height, x, y}; - } - } - - /** - * Returns an integer representation of the AdView's position. - */ - public int getPosition() { - if (mAdView == null || mShouldUseXYForPosition) { - return ConstantsHelper.AD_VIEW_POSITION_UNDEFINED; - } - return mDesiredPosition; - } - - /** - * Displays the {@link PopupWindow} that contains the {@link AdView}, in accordance with the - * parameters of the last call to MoveTo. - * - *

This method must be called on the UI Thread. - * - * @return true if successful, false otherwise. - */ - private boolean updatePopUpLocation(final long callbackDataPtr) { - if (mActivity == null) { - return false; - } - final View view = mActivity.findViewById(android.R.id.content); - if (view == null) { - return false; - } - - // If mActivity's content view doesn't have a window token, it will be - // impossible to update or display the popup later in this method. This is - // a rare case caused by mActivity spinning up or winding down, but it will - // cause the WindowManager to crash. - final View root = view.getRootView(); - if (root == null || root.getWindowToken() == null) { - return false; - } - - synchronized (mPopUpLock) { - if (mPopUp != null) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mPopUpLock) { - // Any change in visibility or position results in the dismissal of the popup (if - // one is being displayed) and creation of a fresh one. - mPopUp.dismiss(); - mPopUp = null; - } - } - }); - } - - mPopUpShowRetryCount = 0; - mPopUpRunnable = new Runnable() { - @Override - public void run() { - int errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - String errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - // If the Activity's window doesn't currently have focus it's not - // possible to display the popup window. Poll the focus after a delay of 10ms and try - // to show the popup again. - if (!mActivity.hasWindowFocus()) { - synchronized (mPopUpLock) { - if (mPopUpRunnable == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else { - if (mPopUpShowRetryCount < POPUP_SHOW_RETRY_COUNT) { - mPopUpShowRetryCount++; - new Handler().postDelayed(mPopUpRunnable, 10); - return; - } - errorCode = ConstantsHelper.CALLBACK_ERROR_NO_WINDOW_TOKEN; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NO_WINDOW_TOKEN; - } - } - } - - if (mAdView == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } - - if (errorCode != ConstantsHelper.CALLBACK_ERROR_NONE) { - completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); - return; - } else if (mPopUp == null) { - mPopUp = new PopupWindow(mAdView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - mPopUp.setBackgroundDrawable(new ColorDrawable(0xFF000000)); // Black - mAdView.getViewTreeObserver().addOnPreDrawListener(AdViewHelper.this); - - if (mShouldUseXYForPosition) { - mPopUp.showAtLocation(root, Gravity.NO_GRAVITY, mDesiredX, mDesiredY); - } else { - switch (mDesiredPosition) { - case ConstantsHelper.AD_VIEW_POSITION_TOP: - mPopUp.showAtLocation(root, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0); - break; - case ConstantsHelper.AD_VIEW_POSITION_BOTTOM: - mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, 0); - break; - case ConstantsHelper.AD_VIEW_POSITION_TOP_LEFT: - mPopUp.showAtLocation(root, Gravity.TOP | Gravity.LEFT, 0, 0); - break; - case ConstantsHelper.AD_VIEW_POSITION_TOP_RIGHT: - mPopUp.showAtLocation(root, Gravity.TOP | Gravity.RIGHT, 0, 0); - break; - case ConstantsHelper.AD_VIEW_POSITION_BOTTOM_LEFT: - mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.LEFT, 0, 0); - break; - case ConstantsHelper.AD_VIEW_POSITION_BOTTOM_RIGHT: - mPopUp.showAtLocation(root, Gravity.BOTTOM | Gravity.RIGHT, 0, 0); - break; - default: - mPopUp.showAtLocation(root, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0); - break; - } - } - } - - completeAdViewFutureCallback(callbackDataPtr, errorCode, errorMessage); - mNotifyBoundingBoxListenerOnNextDraw.set(true); - } - }; - } - - // TODO(b/31391149): This delay is a workaround for b/31391149, and should be removed once - // that bug is resolved. - Handler mainHandler = new Handler(Looper.getMainLooper()); - mainHandler.postDelayed(mPopUpRunnable, WEBVIEW_DELAY_MILLISECONDS); - - return true; - } - - public class AdViewListener extends AdListener implements OnPaidEventListener { - @Override - public void onAdClicked() { - synchronized (mAdViewLock) { - if (mAdViewInternalPtr != CPP_NULLPTR) { - notifyAdClicked(mAdViewInternalPtr); - } - } - super.onAdClicked(); - } - - @Override - public void onAdClosed() { - synchronized (mAdViewLock) { - if (mAdViewInternalPtr != CPP_NULLPTR) { - notifyAdClosed(mAdViewInternalPtr); - mNotifyBoundingBoxListenerOnNextDraw.set(true); - } - } - super.onAdClosed(); - } - - @Override - public void onAdFailedToLoad(LoadAdError loadAdError) { - synchronized (mAdViewLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeAdViewLoadAdError( - mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - super.onAdFailedToLoad(loadAdError); - } - - @Override - public void onAdImpression() { - synchronized (mAdViewLock) { - if (mAdViewInternalPtr != CPP_NULLPTR) { - notifyAdImpression(mAdViewInternalPtr); - } - } - super.onAdImpression(); - } - - @Override - public void onAdLoaded() { - synchronized (mAdViewLock) { - if (mAdView != null) { - mAdViewContainsAd = true; - } - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - AdSize adSize = mAdView.getAdSize(); - completeAdViewLoadedAd(mLoadAdCallbackDataPtr, mAdViewInternalPtr, adSize.getWidth(), - adSize.getHeight(), mAdView.getResponseInfo()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - // Only update the bounding box if the banner view is already visible. - if (mPopUp != null && mPopUp.isShowing()) { - // Loading an ad can sometimes cause the bounds to change. - mNotifyBoundingBoxListenerOnNextDraw.set(true); - } - } - super.onAdLoaded(); - } - - @Override - public void onAdOpened() { - synchronized (mAdViewLock) { - if (mAdViewInternalPtr != CPP_NULLPTR) { - notifyAdOpened(mAdViewInternalPtr); - } - mNotifyBoundingBoxListenerOnNextDraw.set(true); - } - super.onAdOpened(); - } - - public void onPaidEvent(AdValue value) { - synchronized (mAdViewLock) { - if (mAdViewInternalPtr != CPP_NULLPTR) { - notifyPaidEvent(mAdViewInternalPtr, value.getCurrencyCode(), value.getPrecisionType(), - value.getValueMicros()); - } - } - } - } - - /** - * Implementation of ViewTreeObserver.OnPreDrawListener's onPreDraw method. This gets called when - * mAdView is about to be redrawn, and checks a flag before invoking the native callback that - * tells the C++ side a Bounding Box change has occurred and the AdView::Listener (if there is - * one) needs to be notified. - * - *

By invoking the listener callback here, hooked into the draw loop, the AdViewHelper - * object can be sure that any movements of mAdView have been completed and the layout and screen - * position have been recalculated by the time the notification happens, preventing stale data - * from getting to the Listener. - */ - @Override - public boolean onPreDraw() { - if (mNotifyBoundingBoxListenerOnNextDraw.compareAndSet(true, false)) { - if (mAdView != null && mAdViewInternalPtr != CPP_NULLPTR) { - notifyBoundingBoxChanged(mAdViewInternalPtr); - } - } - // Returning true tells Android to continue the draw as normal. - return true; - } - - /** - * Native callback to instruct the C++ wrapper to complete the corresponding future. - */ - public static native void completeAdViewFutureCallback( - long nativeInternalPtr, int errorCode, String errorMessage); - - /** - * Native callback to instruct the C++ wrapper to release its global reference on this - * object. - */ - public static native void releaseAdViewGlobalReferenceCallback(long nativeInternalPtr); - - /** - * Native callback invoked upon successfully loading an ad. - */ - public static native void completeAdViewLoadedAd(long nativeInternalPtr, long mAdViewInternalPtr, - int width, int height, ResponseInfo responseInfo); - - /** - * Native callback upon encountering an error loading an Ad Request. Returns - * Android Google Mobile Ads SDK error codes. - **/ - public static native void completeAdViewLoadAdError( - long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); - - /** - * Native callback upon encountering a wrapper/internal error when - * processing a Load Ad Request. Returns an integer representing - * firebase::gma::AdError codes. - */ - public static native void completeAdViewLoadAdInternalError( - long nativeInternalPtr, int gmaErrorCode, String errorMessage); - - /** - * Native callback to notify the C++ wrapper that the Ad's Bounding Box has changed. - */ - public static native void notifyBoundingBoxChanged(long nativeInternalPtr); - - /** - * Native callback to notify the C++ wrapper of an ad clicked event - */ - public static native void notifyAdClicked(long nativeInternalPtr); - - /** - * Native callback to notify the C++ wrapper of an ad closed event - */ - public static native void notifyAdClosed(long nativeInternalPtr); - - /** - * Native callback to notify the C++ wrapper of an ad impression event - */ - public static native void notifyAdImpression(long nativeInternalPtr); - - /** - * Native callback to notify the C++ wrapper of an ad opened event - */ - public static native void notifyAdOpened(long nativeInternalPtr); - - /** - * Native callback to notify the C++ wrapper that a paid event has occurred. - */ - public static native void notifyPaidEvent( - long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java deleted file mode 100644 index 71da84ff2b..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/ConstantsHelper.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -/** Helper class containing constants that are shared across the various GMA ad formats. */ -public final class ConstantsHelper { - /** Error codes used in completing futures. These match the AdError enumeration in the C++ API. */ - public static final int CALLBACK_ERROR_NONE = 0; - - public static final int CALLBACK_ERROR_UNINITIALIZED = 1; - - public static final int CALLBACK_ERROR_ALREADY_INITIALIZED = 2; - - public static final int CALLBACK_ERROR_LOAD_IN_PROGRESS = 3; - - public static final int CALLBACK_ERROR_INTERNAL_ERROR = 4; - - public static final int CALLBACK_ERROR_INVALID_REQUEST = 5; - - public static final int CALLBACK_ERROR_NETWORK_ERROR = 6; - - public static final int CALLBACK_ERROR_NO_FILL = 7; - - public static final int CALLBACK_ERROR_NO_WINDOW_TOKEN = 8; - - public static final int CALLBACK_ERROR_UNKNOWN = 9; - - /** - * Error messages used for completing futures. These match the error codes in the AdError - * enumeration in the C++ API. - */ - public static final String CALLBACK_ERROR_MESSAGE_NONE = ""; - - public static final String CALLBACK_ERROR_MESSAGE_UNINITIALIZED = - "Ad has not been fully initialized."; - - public static final String CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED = - "Ad is already initialized."; - - public static final String CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS = "Ad is currently loading."; - - public static final String CALLBACK_ERROR_MESSAGE_INTERNAL_ERROR = - "An internal SDK error occurred."; - - public static final String CALLBACK_ERROR_MESSAGE_INVALID_REQUEST = "Invalid ad request."; - - public static final String CALLBACK_ERROR_MESSAGE_NETWORK_ERROR = "A network error occurred."; - - public static final String CALLBACK_ERROR_MESSAGE_NO_FILL = "No ad was available to serve."; - - public static final String CALLBACK_ERROR_MESSAGE_NO_WINDOW_TOKEN = - "Android Activity does not have a window token."; - - public static final String CALLBACK_ERROR_MESSAGE_UNKNOWN = "Unknown error occurred."; - - /** - * Ad view positions (matches the AdView::Position and NativeExpressAdView::Position enumerations - * in the public C++ API). - */ - public static final int AD_VIEW_POSITION_UNDEFINED = -1; - - public static final int AD_VIEW_POSITION_TOP = 0; - - public static final int AD_VIEW_POSITION_BOTTOM = 1; - - public static final int AD_VIEW_POSITION_TOP_LEFT = 2; - - public static final int AD_VIEW_POSITION_TOP_RIGHT = 3; - - public static final int AD_VIEW_POSITION_BOTTOM_LEFT = 4; - - public static final int AD_VIEW_POSITION_BOTTOM_RIGHT = 5; - - /** - * Ad formats (matches the firebase::gma::AdFormat and com.google.android.gms.ads.AdFormat - * enumerations ). - */ - public static final int AD_FORMAT_UNDEFINED = -1; - - public static final int AD_FORMAT_APP_OPEN_AD = 0; - - public static final int AD_FORMAT_BANNER = 1; - - public static final int AD_FORMAT_INTERSTITIAL = 2; - - public static final int AD_FORMAT_NATIVE = 3; - - public static final int AD_FORMAT_REWARDED = 4; - - public static final int AD_FORMAT_REWARDED_INTERSTITIAL = 5; -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java deleted file mode 100644 index 8b1b5dc9b1..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/DownloadHelper.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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.gma.internal.cpp; - -import android.os.AsyncTask; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's - * designed to download static ad assets from network. - */ -public class DownloadHelper { - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // URL to download the asset from. - private URL url; - - // Request headers. - private final HashMap headers; - - // HTTP response code. - private int responseCode; - - // Error message. - private String errorMessage; - - // Error code. - private int errorCode; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to LoadImage. - private long mDownloadCallbackDataPtr; - - // Synchronization object for thread safe access to: - // * mDownloadCallbackDataPtr - private final Object mDownloadLock; - - // Create a new DownloadHelper with a default URL. - public DownloadHelper(String urlString) throws MalformedURLException { - this.headers = new HashMap<>(); - setUrl(urlString); - - mDownloadLock = new Object(); - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - - // Test the callbacks and fail quickly if something's wrong. - completeNativeImageFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - // Set the URL to the given string, or null if it can't be parsed. - public void setUrl(String urlString) throws MalformedURLException { - this.url = new URL(urlString); - } - - // Get the previously-set URL. - public URL getUrl() { - return this.url; - } - - // Add a header key-value pair. - public void addHeader(String key, String value) { - this.headers.put(key, value); - } - - // Clear previously-set headers. - public void clearHeaders() { - this.headers.clear(); - } - - // Get the response code returned by the server, after download() is finished. - public int getResponseCode() { - return this.responseCode; - } - - /** Triggers an async HTTP GET request to the given URL, with the given headers. */ - public void download(long callbackDataPtr) { - synchronized (mDownloadLock) { - if (mDownloadCallbackDataPtr != CPP_NULLPTR) { - completeNativeLoadImageError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - mDownloadCallbackDataPtr = callbackDataPtr; - } - - try { - /** Invokes download as an async background task. */ - new DownloadFilesTask().execute(); - } catch (Exception ex) { - completeNativeLoadImageError( - callbackDataPtr, ConstantsHelper.CALLBACK_ERROR_INTERNAL_ERROR, ex.getMessage()); - } - - return; - } - - /** Performs Download task in a background worker thread. */ - private class DownloadFilesTask extends AsyncTask { - protected byte[] doInBackground(Void... params) { - HttpURLConnection connection = null; - ByteArrayOutputStream bytestream = null; - try { - connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - for (Map.Entry entry : headers.entrySet()) { - connection.setRequestProperty(entry.getKey(), entry.getValue()); - } - - responseCode = connection.getResponseCode(); - - if (responseCode != HttpURLConnection.HTTP_OK) { - errorCode = ConstantsHelper.CALLBACK_ERROR_NETWORK_ERROR; - errorMessage = connection.getResponseMessage(); - - connection.disconnect(); - return null; - } - - InputStream inputStream = connection.getInputStream(); - bytestream = new ByteArrayOutputStream(); - int ch = 0; - - // Buffer to read 1024 bytes in at a time. - byte[] buffer = new byte[1024]; - while ((ch = inputStream.read(buffer)) != -1) { - bytestream.write(buffer, 0, ch); - } - byte imgdata[] = bytestream.toByteArray(); - - bytestream.close(); - connection.disconnect(); - - return imgdata; - } catch (Exception ex) { - errorCode = ConstantsHelper.CALLBACK_ERROR_INVALID_REQUEST; - errorMessage = ex.getMessage(); - } - - try { - if (bytestream != null) { - bytestream.close(); - } - if (connection != null) { - connection.disconnect(); - } - } catch (Exception ex) { - // Connection and bytestream close exceptions can be ignored as download errors are already - // recorded. - } - return null; - } - - protected void onPostExecute(byte[] result) { - synchronized (mDownloadLock) { - if (mDownloadCallbackDataPtr != CPP_NULLPTR) { - if (errorCode != ConstantsHelper.CALLBACK_ERROR_NONE) { - completeNativeLoadImageError(mDownloadCallbackDataPtr, errorCode, errorMessage); - } else { - completeNativeLoadedImage(mDownloadCallbackDataPtr, result); - } - } - } - } - } - - /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ - public static native void completeNativeImageFutureCallback( - long nativeImagePtr, int errorCode, String errorMessage); - - /** Native callback invoked upon successfully downloading an image. */ - public static native void completeNativeLoadedImage(long nativeImagePtr, byte[] image); - - /** Native callback upon encountering an error downloading an image. */ - public static native void completeNativeLoadImageError( - long nativeImagePtr, int errorCode, String errorMessage); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java deleted file mode 100644 index 85aee3c383..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/GmaInitializationHelper.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -import android.content.Context; -import com.google.android.gms.ads.MobileAds; -import com.google.android.gms.ads.initialization.InitializationStatus; -import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; - -/** Helper class for initializing the Google Mobile Ads SDK. */ -public final class GmaInitializationHelper { - public static void initializeGma(Context context) { - MobileAds.initialize(context, new OnInitializationCompleteListener() { - @Override - public void onInitializationComplete(InitializationStatus initializationStatus) { - initializationCompleteCallback(initializationStatus); - } - }); - } - - public static native void initializationCompleteCallback( - InitializationStatus initializationStatus); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java deleted file mode 100644 index e08d82345a..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/InterstitialAdHelper.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -import android.app.Activity; -import android.util.Log; -import com.google.android.gms.ads.AdError; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdValue; -import com.google.android.gms.ads.FullScreenContentCallback; -import com.google.android.gms.ads.LoadAdError; -import com.google.android.gms.ads.OnPaidEventListener; -import com.google.android.gms.ads.ResponseInfo; -import com.google.android.gms.ads.interstitial.InterstitialAd; -import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java {@link InterstitialAd} - * objects cleaner. It's designed to wrap and adapt a single instance of {@link InterstitialAd}, - * translate calls coming from C++ into their (typically more complicated) Java equivalents. - */ -public class InterstitialAdHelper { - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // Pointer to the InterstitialAdInternalAndroid object that created this - // object. - private long mInterstitialAdInternalPtr; - - // The GMA SDK {@link InterstitialAd} associated with this helper. - private InterstitialAd mInterstitial; - - // Synchronization object for thread safe access to: - // * mInterstitial - // * mInterstitialAdInternalPtr - // * mLoadAdCallbackDataPtr - private final Object mInterstitialLock; - - // The {@link Activity} this helper uses to display its - // {@link InterstitialAd}. - private Activity mActivity; - - // The ad unit ID to use for the {@link InterstitialAd}. - private String mAdUnitId; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to LoadAd. - private long mLoadAdCallbackDataPtr; - - /** - * Constructor. - */ - public InterstitialAdHelper(long interstitialAdInternalPtr) { - mInterstitialAdInternalPtr = interstitialAdInternalPtr; - mInterstitialLock = new Object(); - - // Test the callbacks and fail quickly if something's wrong. - completeInterstitialAdFutureCallback( - CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Initializes the {@link InterstitialAd}. This creates the corresponding GMA SDK {@link - * InterstitialAd} object and sets it up. - */ - public void initialize(final long callbackDataPtr, Activity activity) { - mActivity = activity; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - int errorCode; - String errorMessage; - if (mInterstitial == null) { - try { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - } catch (IllegalStateException e) { - mInterstitial = null; - // This exception can be thrown if the ad unit ID was already set. - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - completeInterstitialAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - }); - } - - /** Disconnect the helper from the interstital ad. */ - public void disconnect() { - synchronized (mInterstitialLock) { - mInterstitialAdInternalPtr = CPP_NULLPTR; - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - - if (mActivity == null) { - return; - } - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mInterstitialLock) { - if (mInterstitial != null) { - mInterstitial.setFullScreenContentCallback(null); - mInterstitial.setOnPaidEventListener(null); - mInterstitial = null; - } - } - } - }); - } - - /** Loads an ad for the underlying {@link InterstitialAd} object. */ - public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { - if (mActivity == null) { - return; - } - synchronized (mInterstitialLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeInterstitialLoadAdInternalError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - mLoadAdCallbackDataPtr = callbackDataPtr; - } - - mAdUnitId = adUnitId; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mActivity == null) { - synchronized (mInterstitialLock) { - completeInterstitialLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } else { - try { - InterstitialAd.load(mActivity, mAdUnitId, request, new InterstitialAdListener()); - } catch (IllegalStateException e) { - synchronized (mInterstitialLock) { - completeInterstitialLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - }); - } - - /** Shows a previously loaded ad. */ - public void show(final long callbackDataPtr) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mInterstitialLock) { - int errorCode; - String errorMessage; - if (mAdUnitId == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else if (mInterstitial == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - mInterstitial.show(mActivity); - } - completeInterstitialAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - } - }); - } - - private class InterstitialAdFullScreenContentListener - extends FullScreenContentCallback implements OnPaidEventListener { - @Override - public void onAdClicked() { - synchronized (mInterstitialLock) { - if (mInterstitialAdInternalPtr != CPP_NULLPTR) { - notifyAdClickedFullScreenContentEvent(mInterstitialAdInternalPtr); - } - } - } - - @Override - public void onAdDismissedFullScreenContent() { - synchronized (mInterstitialLock) { - if (mInterstitialAdInternalPtr != CPP_NULLPTR) { - notifyAdDismissedFullScreenContentEvent(mInterstitialAdInternalPtr); - } - } - } - - @Override - public void onAdFailedToShowFullScreenContent(AdError error) { - synchronized (mInterstitialLock) { - if (mInterstitialAdInternalPtr != CPP_NULLPTR) { - notifyAdFailedToShowFullScreenContentEvent(mInterstitialAdInternalPtr, error); - } - } - } - - @Override - public void onAdImpression() { - synchronized (mInterstitialLock) { - if (mInterstitialAdInternalPtr != CPP_NULLPTR) { - notifyAdImpressionEvent(mInterstitialAdInternalPtr); - } - } - } - - @Override - public void onAdShowedFullScreenContent() { - synchronized (mInterstitialLock) { - if (mInterstitialAdInternalPtr != CPP_NULLPTR) { - notifyAdShowedFullScreenContentEvent(mInterstitialAdInternalPtr); - } - } - } - - public void onPaidEvent(AdValue value) { - synchronized (mInterstitialLock) { - notifyPaidEvent(mInterstitialAdInternalPtr, value.getCurrencyCode(), - value.getPrecisionType(), value.getValueMicros()); - } - } - } - - private class InterstitialAdListener extends InterstitialAdLoadCallback { - @Override - public void onAdFailedToLoad(LoadAdError loadAdError) { - synchronized (mInterstitialLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeInterstitialLoadAdError( - mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - - @Override - public void onAdLoaded(InterstitialAd ad) { - synchronized (mInterstitialLock) { - mInterstitial = ad; - InterstitialAdFullScreenContentListener listener = - new InterstitialAdFullScreenContentListener(); - mInterstitial.setFullScreenContentCallback(listener); - mInterstitial.setOnPaidEventListener(listener); - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeInterstitialLoadedAd(mLoadAdCallbackDataPtr, ad.getResponseInfo()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - - /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ - public static native void completeInterstitialAdFutureCallback( - long nativeInternalPtr, int errorCode, String errorMessage); - - /** Native callback invoked upon successfully loading an ad. */ - public static native void completeInterstitialLoadedAd( - long nativeInternalPtr, ResponseInfo responseInfo); - - /** - * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile - * Ads SDK error codes. - */ - public static native void completeInterstitialLoadAdError( - long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); - - /** - * Native callback upon encountering a wrapper/internal error when processing an Ad Request. - * Returns integers representing firebase::gma::AdError codes. - */ - public static native void completeInterstitialLoadAdInternalError( - long nativeInternalPtr, int gmaErrorCode, String errorMessage); - - /** Native callbacks to notify the C++ wrapper of ad events */ - public static native void notifyAdClickedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyAdDismissedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyAdFailedToShowFullScreenContentEvent( - long nativeInternalPtr, AdError adError); - - public static native void notifyAdImpressionEvent(long nativeInternalPtr); - - public static native void notifyAdShowedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyPaidEvent( - long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java deleted file mode 100644 index a2829e8bcb..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/NativeAdHelper.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * 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.gma.internal.cpp; - -import android.app.Activity; -import android.os.Bundle; -import com.google.android.gms.ads.AdListener; -import com.google.android.gms.ads.AdLoader; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.LoadAdError; -import com.google.android.gms.ads.ResponseInfo; -import com.google.android.gms.ads.nativead.NativeAd; -import java.util.List; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's - * designed to wrap and adapt a single instance of NativeAd, translate calls coming from C++ into - * their (typically more complicated) Java equivalents. - */ -public class NativeAdHelper { - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // Pointer to the NativeAdInternalAndroid object that created this - // object. - private long mNativeAdInternalPtr; - - // The GMA SDK NativeAd associated with this helper. - private NativeAd mNative; - - // Synchronization object for thread safe access to: - // * mNative - // * mNativeAdInternalPtr - // * mLoadAdCallbackDataPtr - private final Object mNativeLock; - - // The Activity this helper uses to display its NativeAd. - private Activity mActivity; - - // The ad unit ID to use for the NativeAd. - private String mAdUnitId; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to LoadAd. - private long mLoadAdCallbackDataPtr; - - /** Constructor. */ - public NativeAdHelper(long nativeAdInternalPtr) { - mNativeAdInternalPtr = nativeAdInternalPtr; - mNativeLock = new Object(); - - // Test the callbacks and fail quickly if something's wrong. - completeNativeAdFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Initializes the NativeAd. This creates the corresponding GMA SDK NativeAd object and sets it - * up. - */ - public void initialize(final long callbackDataPtr, Activity activity) { - mActivity = activity; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - int errorCode; - String errorMessage; - if (mNative == null) { - try { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - } catch (IllegalStateException e) { - mNative = null; - // This exception can be thrown if the ad unit ID was already set. - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - }); - } - - /** Disconnect the helper from the native ad. */ - public void disconnect() { - synchronized (mNativeLock) { - mNativeAdInternalPtr = CPP_NULLPTR; - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - - if (mActivity == null) { - return; - } - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mNativeLock) { - if (mNative != null) { - mNative = null; - } - } - } - }); - } - - /** Record Impression for allowlisted ad units. */ - public void recordImpression(final long callbackDataPtr, final Bundle payload) { - if (mActivity == null) { - return; - } - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mNativeLock) { - int errorCode; - String errorMessage; - if (mAdUnitId == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else if (mNative == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - mNative.recordImpression(payload); - } - completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - } - }); - } - - /** Perform click for allowlisted ad units. */ - public void performClick(final long callbackDataPtr, final Bundle payload) { - if (mActivity == null) { - return; - } - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mNativeLock) { - int errorCode; - String errorMessage; - if (mAdUnitId == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else if (mNative == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - mNative.performClick(payload); - } - completeNativeAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - } - }); - } - - /** Loads an ad for the underlying NativeAd object. */ - public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { - if (mActivity == null) { - return; - } - synchronized (mNativeLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeNativeLoadAdInternalError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - mLoadAdCallbackDataPtr = callbackDataPtr; - } - - mAdUnitId = adUnitId; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (mActivity == null) { - synchronized (mNativeLock) { - completeNativeLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } else { - try { - AdLoader.Builder adLoaderBuilder = new AdLoader.Builder(mActivity, mAdUnitId); - NativeAdListener listener = new NativeAdListener(); - adLoaderBuilder.forNativeAd(listener); - adLoaderBuilder.withAdListener(listener); - AdLoader adLoader = adLoaderBuilder.build(); - adLoader.loadAd(request); - } catch (IllegalStateException e) { - synchronized (mNativeLock) { - completeNativeLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - }); - } - - private class NativeAdListener extends AdListener implements NativeAd.OnNativeAdLoadedListener { - @Override - public void onAdFailedToLoad(LoadAdError loadAdError) { - synchronized (mNativeLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeNativeLoadAdError( - mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - - public void onNativeAdLoaded(NativeAd ad) { - synchronized (mNativeLock) { - mNative = ad; - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - List imgList = ad.getImages(); - NativeAd.Image[] imgArray = new NativeAd.Image[imgList.size()]; - imgArray = imgList.toArray(imgArray); - - NativeAd.Image adChoicesIcon = null; - NativeAd.AdChoicesInfo adChoicesInfo = ad.getAdChoicesInfo(); - if (adChoicesInfo != null) { - List adChoicesImgList = adChoicesInfo.getImages(); - if (!adChoicesImgList.isEmpty()) { - // Gets only the first image to keep the api in sync with its ios counterpart. - adChoicesIcon = adChoicesImgList.get(0); - } - } - - completeNativeLoadedAd(mLoadAdCallbackDataPtr, mNativeAdInternalPtr, ad.getIcon(), - imgArray, adChoicesIcon, ad.getResponseInfo()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - - @Override - public void onAdClicked() { - synchronized (mNativeLock) { - if (mNativeAdInternalPtr != CPP_NULLPTR) { - notifyAdClicked(mNativeAdInternalPtr); - } - } - } - - @Override - public void onAdImpression() { - synchronized (mNativeLock) { - if (mNativeAdInternalPtr != CPP_NULLPTR) { - notifyAdImpression(mNativeAdInternalPtr); - } - } - } - - @Override - public void onAdClosed() { - synchronized (mNativeLock) { - if (mNativeAdInternalPtr != CPP_NULLPTR) { - notifyAdClosed(mNativeAdInternalPtr); - } - } - } - - @Override - public void onAdOpened() { - synchronized (mNativeLock) { - if (mNativeAdInternalPtr != CPP_NULLPTR) { - notifyAdOpened(mNativeAdInternalPtr); - } - } - } - } - - /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ - public static native void completeNativeAdFutureCallback( - long nativeInternalPtr, int errorCode, String errorMessage); - - /** Native callback invoked upon successfully loading an ad. */ - public static native void completeNativeLoadedAd(long nativeInternalPtr, - long mNativeAdInternalPtr, NativeAd.Image icon, NativeAd.Image[] images, - NativeAd.Image adChoicesIcon, ResponseInfo responseInfo); - - /** - * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile - * Ads SDK error codes. - */ - public static native void completeNativeLoadAdError( - long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); - - /** - * Native callback upon encountering a wrapper/internal error when processing an Ad Request. - * Returns integers representing firebase::gma::AdError codes. - */ - public static native void completeNativeLoadAdInternalError( - long nativeInternalPtr, int gmaErrorCode, String errorMessage); - - /** Native callback to notify the C++ wrapper of an ad clicked event */ - public static native void notifyAdClicked(long nativeInternalPtr); - - /** Native callback to notify the C++ wrapper of an ad closed event */ - public static native void notifyAdClosed(long nativeInternalPtr); - - /** Native callback to notify the C++ wrapper of an ad impression event */ - public static native void notifyAdImpression(long nativeInternalPtr); - - /** Native callback to notify the C++ wrapper of an ad opened event */ - public static native void notifyAdOpened(long nativeInternalPtr); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java deleted file mode 100644 index 644e905d05..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/QueryInfoHelper.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2024 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.gma.internal.cpp; - -import android.app.Activity; -import com.google.android.gms.ads.AdFormat; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.query.QueryInfo; -import com.google.android.gms.ads.query.QueryInfoGenerationCallback; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java objects cleaner. It's - * designed to wrap and adapt a single instance of QueryInfo, translate calls coming from C++ into - * their (typically more complicated) Java equivalents. - */ -public class QueryInfoHelper { - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // Pointer to the QueryInfoInternalAndroid object that created this - // object. - private long queryInfoInternalPtr; - - // The GMA SDK QueryInfo associated with this helper. - private QueryInfo gmaQueryInfo; - - // Synchronization object for thread safe access to: - // * queryInfoInternalPtr - // * createQueryInfoCallbackDataPtr - private final Object queryInfoLock; - - // The Activity this helper uses to generate the QueryInfo. - private Activity activity; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to CreateQueryInfo. - private long createQueryInfoCallbackDataPtr; - - /** Constructor. */ - public QueryInfoHelper(long queryInfoInternalPtr) { - this.queryInfoInternalPtr = queryInfoInternalPtr; - queryInfoLock = new Object(); - - // Test the callbacks and fail quickly if something's wrong. - completeQueryInfoFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Initializes the QueryInfoHelper. This creates the corresponding GMA SDK NativeAd object and - * sets it up. - */ - public void initialize(final long callbackDataPtr, Activity activity) { - this.activity = activity; - - this.activity.runOnUiThread(new Runnable() { - @Override - public void run() { - int errorCode; - String errorMessage; - if (gmaQueryInfo == null) { - try { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - } catch (IllegalStateException e) { - gmaQueryInfo = null; - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - completeQueryInfoFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - }); - } - - /** Disconnect the helper from the query info. */ - public void disconnect() { - synchronized (queryInfoLock) { - queryInfoInternalPtr = CPP_NULLPTR; - createQueryInfoCallbackDataPtr = CPP_NULLPTR; - } - - if (activity == null) { - return; - } - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (queryInfoLock) { - if (gmaQueryInfo != null) { - gmaQueryInfo = null; - } - } - } - }); - } - - private AdFormat getAdFormat(int format) { - switch (format) { - case ConstantsHelper.AD_FORMAT_BANNER: - return AdFormat.BANNER; - case ConstantsHelper.AD_FORMAT_INTERSTITIAL: - return AdFormat.INTERSTITIAL; - case ConstantsHelper.AD_FORMAT_REWARDED: - return AdFormat.REWARDED; - case ConstantsHelper.AD_FORMAT_NATIVE: - return AdFormat.NATIVE; - case ConstantsHelper.AD_FORMAT_REWARDED_INTERSTITIAL: - return AdFormat.REWARDED_INTERSTITIAL; - default: - return AdFormat.APP_OPEN_AD; - } - } - - /** Creates a query info for the underlying QueryInfo object. */ - public void createQueryInfo( - long callbackDataPtr, int format, String adUnitId, final AdRequest request) { - if (activity == null) { - return; - } - synchronized (queryInfoLock) { - if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { - completeCreateQueryInfoError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - createQueryInfoCallbackDataPtr = callbackDataPtr; - } - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - if (activity == null) { - synchronized (queryInfoLock) { - completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - createQueryInfoCallbackDataPtr = CPP_NULLPTR; - } - } else { - try { - AdFormat adFormat = getAdFormat(format); - if (adUnitId != null && !adUnitId.isEmpty()) { - QueryInfo.generate(activity, adFormat, request, adUnitId, new QueryInfoListener()); - } else { - QueryInfo.generate(activity, adFormat, request, new QueryInfoListener()); - } - } catch (IllegalStateException e) { - synchronized (queryInfoLock) { - completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - createQueryInfoCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - }); - } - - private class QueryInfoListener extends QueryInfoGenerationCallback { - @Override - public void onFailure(String errorMessage) { - synchronized (queryInfoLock) { - if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { - completeCreateQueryInfoError(createQueryInfoCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_INVALID_REQUEST, errorMessage); - createQueryInfoCallbackDataPtr = CPP_NULLPTR; - } - } - } - - @Override - public void onSuccess(QueryInfo queryInfo) { - synchronized (queryInfoLock) { - gmaQueryInfo = queryInfo; - if (createQueryInfoCallbackDataPtr != CPP_NULLPTR) { - completeCreateQueryInfoSuccess(createQueryInfoCallbackDataPtr, queryInfo.getQuery()); - createQueryInfoCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - - /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ - public static native void completeQueryInfoFutureCallback( - long internalPtr, int errorCode, String errorMessage); - - /** Native callback invoked upon successfully generating a QueryInfo. */ - public static native void completeCreateQueryInfoSuccess( - long createQueryInfoInternalPtr, String query); - - /** - * Native callback invoked upon error generating a QueryInfo. Also used for wrapper/internal - * errors when processing a query info generation request. Returns integers representing - * firebase::gma::AdError codes. - */ - public static native void completeCreateQueryInfoError( - long createQueryInfoInternalPtr, int gmaErrorCode, String errorMessage); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java b/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java deleted file mode 100644 index 3e78901d2f..0000000000 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/RewardedAdHelper.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2021 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.gma.internal.cpp; - -import android.app.Activity; -import android.util.Log; -import com.google.android.gms.ads.AdError; -import com.google.android.gms.ads.AdRequest; -import com.google.android.gms.ads.AdValue; -import com.google.android.gms.ads.FullScreenContentCallback; -import com.google.android.gms.ads.LoadAdError; -import com.google.android.gms.ads.OnPaidEventListener; -import com.google.android.gms.ads.OnUserEarnedRewardListener; -import com.google.android.gms.ads.ResponseInfo; -import com.google.android.gms.ads.rewarded.RewardItem; -import com.google.android.gms.ads.rewarded.RewardedAd; -import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback; -import com.google.android.gms.ads.rewarded.ServerSideVerificationOptions; - -/** - * Helper class to make interactions between the GMA C++ wrapper and Java {@link RewardedAd} objects - * cleaner. It's designed to wrap and adapt a single instance of {@link RewardedAd}, translate calls - * coming from C++ into their (typically more complicated) Java equivalents. - */ -public class RewardedAdHelper { - // C++ nullptr for use with the callbacks. - private static final long CPP_NULLPTR = 0; - - // Pointer to the RewardedAdInternalAndroid object that created this - // object. - private long mRewardedAdInternalPtr; - - // The GMA SDK {@link RewardedAd} associated with this helper. - private RewardedAd mRewarded; - - // Synchronization object for thread safe access to: - // * mRewarded - // * mRewardedAdInternalPtr - // * mLoadAdCallbackDataPtr - private final Object mRewardedLock; - - // The {@link Activity} this helper uses to display its - // {@link RewardedAd}. - private Activity mActivity; - - // The ad unit ID to use for the {@link RewardedAd}. - private String mAdUnitId; - - // Pointer to a FutureCallbackData in the C++ wrapper that will be used to - // complete the Future associated with the latest call to LoadAd. - private long mLoadAdCallbackDataPtr; - - /** Constructor. */ - public RewardedAdHelper(long rewardedAdInternalPtr) { - mRewardedAdInternalPtr = rewardedAdInternalPtr; - mRewardedLock = new Object(); - - // Test the callbacks and fail quickly if something's wrong. - completeRewardedAdFutureCallback(CPP_NULLPTR, 0, ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE); - } - - /** - * Initializes the {@link RewardedAd}. This creates the corresponding GMA SDK {@link RewardedAd} - * object and sets it up. - */ - public void initialize(final long callbackDataPtr, Activity activity) { - mActivity = activity; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - int errorCode; - String errorMessage; - synchronized (mRewardedLock) { - if (mRewarded == null) { - try { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - } catch (IllegalStateException e) { - mRewarded = null; - // This exception can be thrown if the ad unit ID was already set. - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_ALREADY_INITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_ALREADY_INITIALIZED; - } - completeRewardedAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - } - }); - } - - /** Disconnect the helper from the interstital ad. */ - public void disconnect() { - synchronized (mRewardedLock) { - mRewardedAdInternalPtr = CPP_NULLPTR; - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - - if (mActivity == null) { - return; - } - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mRewardedLock) { - if (mRewarded != null) { - mRewarded.setFullScreenContentCallback(null); - mRewarded.setOnPaidEventListener(null); - mRewarded = null; - } - } - } - }); - } - - /** Loads an ad for the underlying {@link RewardedAd} object. */ - public void loadAd(long callbackDataPtr, String adUnitId, final AdRequest request) { - if (mActivity == null) { - return; - } - synchronized (mRewardedLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeRewardedLoadAdInternalError(callbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS); - return; - } - mLoadAdCallbackDataPtr = callbackDataPtr; - } - - mAdUnitId = adUnitId; - - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mRewardedLock) { - if (mActivity == null) { - completeRewardedLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } else { - try { - RewardedAd.load(mActivity, mAdUnitId, request, new RewardedAdListener()); - } catch (IllegalStateException e) { - completeRewardedLoadAdInternalError(mLoadAdCallbackDataPtr, - ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED, - ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - }); - } - - /** - * Shows a previously loaded ad. - */ - public void show(final long callbackDataPtr, final String verificationCustomData, - final String verificationUserId) { - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - synchronized (mRewardedLock) { - int errorCode; - String errorMessage; - if (mAdUnitId == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_UNINITIALIZED; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_UNINITIALIZED; - } else if (mRewarded == null) { - errorCode = ConstantsHelper.CALLBACK_ERROR_LOAD_IN_PROGRESS; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_LOAD_IN_PROGRESS; - } else { - errorCode = ConstantsHelper.CALLBACK_ERROR_NONE; - errorMessage = ConstantsHelper.CALLBACK_ERROR_MESSAGE_NONE; - if (!verificationCustomData.isEmpty() || !verificationUserId.isEmpty()) { - ServerSideVerificationOptions options = new ServerSideVerificationOptions.Builder() - .setCustomData(verificationCustomData) - .setUserId(verificationUserId) - .build(); - mRewarded.setServerSideVerificationOptions(options); - } - mRewarded.show(mActivity, new UserEarnedRewardListener()); - } - completeRewardedAdFutureCallback(callbackDataPtr, errorCode, errorMessage); - } - } - }); - } - - private class UserEarnedRewardListener implements OnUserEarnedRewardListener { - @Override - public void onUserEarnedReward(RewardItem rewardItem) { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyUserEarnedRewardEvent( - mRewardedAdInternalPtr, rewardItem.getType(), rewardItem.getAmount()); - } - } - } - } - - private class RewardedAdFullScreenContentListener - extends FullScreenContentCallback implements OnPaidEventListener { - @Override - public void onAdClicked() { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyAdClickedFullScreenContentEvent(mRewardedAdInternalPtr); - } - } - } - - @Override - public void onAdDismissedFullScreenContent() { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyAdDismissedFullScreenContentEvent(mRewardedAdInternalPtr); - } - } - } - - @Override - public void onAdFailedToShowFullScreenContent(AdError error) { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyAdFailedToShowFullScreenContentEvent(mRewardedAdInternalPtr, error); - } - } - } - - @Override - public void onAdImpression() { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyAdImpressionEvent(mRewardedAdInternalPtr); - } - } - } - - @Override - public void onAdShowedFullScreenContent() { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyAdShowedFullScreenContentEvent(mRewardedAdInternalPtr); - } - } - } - - public void onPaidEvent(AdValue value) { - synchronized (mRewardedLock) { - if (mRewardedAdInternalPtr != CPP_NULLPTR) { - notifyPaidEvent(mRewardedAdInternalPtr, value.getCurrencyCode(), value.getPrecisionType(), - value.getValueMicros()); - } - } - } - } - - private class RewardedAdListener extends RewardedAdLoadCallback { - @Override - public void onAdFailedToLoad(LoadAdError loadAdError) { - synchronized (mRewardedLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - completeRewardedLoadAdError( - mLoadAdCallbackDataPtr, loadAdError, loadAdError.getCode(), loadAdError.getMessage()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - - @Override - public void onAdLoaded(RewardedAd ad) { - synchronized (mRewardedLock) { - if (mLoadAdCallbackDataPtr != CPP_NULLPTR) { - mRewarded = ad; - RewardedAdFullScreenContentListener listener = new RewardedAdFullScreenContentListener(); - mRewarded.setFullScreenContentCallback(listener); - mRewarded.setOnPaidEventListener(listener); - completeRewardedLoadedAd(mLoadAdCallbackDataPtr, mRewarded.getResponseInfo()); - mLoadAdCallbackDataPtr = CPP_NULLPTR; - } - } - } - } - - /** Native callback to instruct the C++ wrapper to complete the corresponding future. */ - public static native void completeRewardedAdFutureCallback( - long nativeInternalPtr, int errorCode, String errorMessage); - - /** Native callback invoked upon successfully loading an ad. */ - public static native void completeRewardedLoadedAd( - long nativeInternalPtr, ResponseInfo responseInfo); - - /** - * Native callback upon encountering an error loading an Ad Request. Returns Android Google Mobile - * Ads SDK error codes. - */ - public static native void completeRewardedLoadAdError( - long nativeInternalPtr, LoadAdError error, int errorCode, String errorMessage); - - /** - * Native callback upon encountering a wrapper/internal error when processing an Ad Request. - * Returns integers representing firebase::gma::AdError codes. - */ - public static native void completeRewardedLoadAdInternalError( - long nativeInternalPtr, int gmaErrorCode, String errorMessage); - - /** Native callbacks to notify the C++ wrapper of ad events */ - public static native void notifyUserEarnedRewardEvent( - long mRewardedAdInternalPtr, String type, int amount); - - public static native void notifyAdClickedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyAdDismissedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyAdFailedToShowFullScreenContentEvent( - long nativeInternalPtr, AdError adError); - - public static native void notifyAdImpressionEvent(long nativeInternalPtr); - - public static native void notifyAdShowedFullScreenContentEvent(long nativeInternalPtr); - - public static native void notifyPaidEvent( - long nativeInternalPtr, String currencyCode, int precisionType, long valueMicros); -} diff --git a/ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java b/ump/src_java/com/google/firebase/ump/internal/cpp/ConsentInfoHelper.java similarity index 98% rename from ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java rename to ump/src_java/com/google/firebase/ump/internal/cpp/ConsentInfoHelper.java index 4b2bf1852b..637d24fae2 100644 --- a/ump/src_java/com/google/firebase/gma/internal/cpp/ConsentInfoHelper.java +++ b/ump/src_java/com/google/firebase/ump/internal/cpp/ConsentInfoHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.firebase.gma.internal.cpp; +package com.google.firebase.ump.internal.cpp; import android.app.Activity; import android.content.Context; @@ -34,7 +34,7 @@ import java.util.ArrayList; /** - * Helper class to make interactions between the GMA UMP C++ wrapper and the Android UMP API. + * Helper class to make interactions between the UMP C++ wrapper and the Android UMP API. */ public class ConsentInfoHelper { // C++ nullptr for use with the callbacks. From c72165091e690c308875e5eb65b7088c128f8771 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:44:35 -0700 Subject: [PATCH 04/27] Update integration test. --- ump/integration_test/CMakeLists.txt | 2 +- ump/integration_test/Podfile | 3 +- ump/integration_test/build.gradle | 2 +- ump/integration_test/src/integration_test.cc | 2837 ++---------------- 4 files changed, 175 insertions(+), 2669 deletions(-) diff --git a/ump/integration_test/CMakeLists.txt b/ump/integration_test/CMakeLists.txt index 29c6a6e4d1..5db68bc397 100644 --- a/ump/integration_test/CMakeLists.txt +++ b/ump/integration_test/CMakeLists.txt @@ -236,7 +236,7 @@ endif() # Add the Firebase libraries to the target using the function from the SDK. add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) # Note that firebase_app needs to be last in the list. -set(firebase_libs firebase_gma firebase_app) +set(firebase_libs firebase_ump firebase_app) set(gtest_libs gtest gmock) target_link_libraries(${integration_test_target_name} ${firebase_libs} ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/ump/integration_test/Podfile b/ump/integration_test/Podfile index 932579bd3b..bb14f01810 100644 --- a/ump/integration_test/Podfile +++ b/ump/integration_test/Podfile @@ -1,12 +1,11 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '13.0' -# Firebase GMA test application. +# Firebase UMP test application. use_frameworks! :linkage => :static target 'integration_test' do platform :ios, '13.0' pod 'Firebase/CoreOnly', '11.10.0' - pod 'Google-Mobile-Ads-SDK', '11.2.0' pod 'GoogleUserMessagingPlatform', '2.3.0' end diff --git a/ump/integration_test/build.gradle b/ump/integration_test/build.gradle index 41d156e8eb..2b6eec4e30 100644 --- a/ump/integration_test/build.gradle +++ b/ump/integration_test/build.gradle @@ -84,7 +84,7 @@ android { apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" firebaseCpp.dependencies { - gma + ump } apply plugin: 'com.google.gms.google-services' diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc index e26beb6ff3..2ee924b772 100644 --- a/ump/integration_test/src/integration_test.cc +++ b/ump/integration_test/src/integration_test.cc @@ -24,9 +24,7 @@ #include "app_framework.h" // NOLINT #include "firebase/app.h" -#include "firebase/gma.h" -#include "firebase/gma/types.h" -#include "firebase/gma/ump.h" +#include "firebase/ump.h" #include "firebase/util.h" #include "firebase_test_framework.h" // NOLINT @@ -50,93 +48,12 @@ namespace firebase_testapp_automated { -// The GMA app IDs for the test app. -#if defined(ANDROID) -// If you change the GMA app ID for your Android app, make sure to change it -// in AndroidManifest.xml as well. -const char* kGmaAppID = "ca-app-pub-3940256099942544~3347511713"; -#else -// If you change the GMA app ID for your iOS app, make sure to change the -// value for "GADApplicationIdentifier" in your Info.plist as well. -const char* kGmaAppID = "ca-app-pub-3940256099942544~1458002511"; -#endif - -// These ad units IDs have been created specifically for testing, and will -// always return test ads. -#if defined(ANDROID) -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; -const char* kRewardedAdUnit = "ca-app-pub-3940256099942544/5224354917"; -const char* kNativeAdUnit = "ca-app-pub-3940256099942544/2247696110"; -#else -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; -const char* kRewardedAdUnit = "ca-app-pub-3940256099942544/1712485313"; -const char* kNativeAdUnit = "ca-app-pub-3940256099942544/3986624511"; -#endif - -// Used in a test to send an errant ad unit id. -const char* kBadAdUnit = "oops"; - -// Standard Banner Ad Size. -static const int kBannerWidth = 320; -static const int kBannerHeight = 50; - -enum AdCallbackEvent { - AdCallbackEventClicked = 0, - AdCallbackEventClosed, - AdCallbackEventAdImpression, - AdCallbackEventOpened, - AdCallbackEventPaidEvent -}; - -// Error domains vary across phone SDKs. -#if defined(ANDROID) -const char* kErrorDomain = "com.google.android.gms.ads"; -#else -const char* kErrorDomain = "com.google.admob"; -#endif - // Sample test device IDs to use in making the request. -// You can replace these with actual device IDs for certain tests (e.g. UMP) +// You can replace these with actual device IDs for UMP tests // to work on hardware devices. const std::vector kTestDeviceIDs = { "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; -// Sample keywords to use in making the request. -static const std::vector kKeywords({"GMA", "C++", "Fun"}); - -// "Extra" key value pairs can be added to the request as well. Typically -// these are used when testing new features. -static const std::map kGmaAdapterExtras = { - {"the_name_of_an_extra", "the_value_for_that_extra"}, - {"heres", "a second example"}}; - -#if defined(ANDROID) -static const char* kAdNetworkExtrasClassName = - "com/google/ads/mediation/admob/AdMobAdapter"; -#else -static const char* kAdNetworkExtrasClassName = "GADExtras"; -#endif - -// Class nname of the GMA SDK returned in initialization results. -#if defined(ANDROID) -const char kGmaClassName[] = "com.google.android.gms.ads.MobileAds"; -#elif defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE -const char kGmaClassName[] = "GADMobileAds"; -#else // desktop -const char kGmaClassName[] = "stub"; -#endif - -// Used to detect kAdErrorCodeAdNetworkClassLoadErrors when loading -// ads. -static const char* kAdNetworkExtrasInvalidClassName = "abc123321cba"; - -static const char* kContentUrl = "http://www.firebase.com"; - -static const std::vector kNeighboringContentURLs = { - "test_url1", "test_url2", "test_url3"}; - using app_framework::LogDebug; using app_framework::LogInfo; using app_framework::LogWarning; @@ -152,78 +69,26 @@ using testing::HasSubstr; using testing::Pair; using testing::Property; -class FirebaseGmaTest : public FirebaseTest { +class FirebaseUmpTest : public FirebaseTest { public: - FirebaseGmaTest(); - ~FirebaseGmaTest() override; + FirebaseUmpTest() : consent_info_(nullptr) {} + + // Whether to call ConsentInfo::Reset() upon initialization, which + // resets UMP's consent state to as if the app was first installed. + enum ResetOption { kReset, kNoReset }; - static void SetUpTestSuite(); - static void TearDownTestSuite(); + void InitializeUmp(ResetOption reset = kReset); + void TerminateUmp(ResetOption reset = kReset); void SetUp() override; void TearDown() override; protected: - firebase::gma::AdRequest GetAdRequest(); - firebase::Variant GetVariantMap(); - static firebase::App* shared_app_; + firebase::ump::ConsentInfo* consent_info_; }; -class FirebaseGmaUITest : public FirebaseGmaTest { - public: - FirebaseGmaUITest(); - ~FirebaseGmaUITest() override; - - void SetUp() override; -}; - -class FirebaseGmaMinimalTest : public FirebaseTest { - public: - FirebaseGmaMinimalTest(); - ~FirebaseGmaMinimalTest() override; -}; - -// Runs GMA Tests on methods and functions that should be run -// before GMA initializes. -class FirebaseGmaPreInitializationTests : public FirebaseGmaTest { - public: - FirebaseGmaPreInitializationTests(); - ~FirebaseGmaPreInitializationTests() override; - - static void SetUpTestSuite(); - - void SetUp() override; -}; - -firebase::App* FirebaseGmaTest::shared_app_ = nullptr; - -void PauseForVisualInspectionAndCallbacks() { - app_framework::ProcessEvents(300); -} - -void InitializeGma(firebase::App* shared_app) { - LogDebug("Initializing GMA."); - - ::firebase::ModuleInitializer initializer; - initializer.Initialize(shared_app, nullptr, - [](::firebase::App* app, void* /* userdata */) { - LogDebug("Try to initialize GMA"); - firebase::InitResult result; - ::firebase::gma::Initialize(*app, &result); - return result; - }); - - FirebaseGmaTest::WaitForCompletion(initializer.InitializeLastResult(), - "Initialize"); - - ASSERT_EQ(initializer.InitializeLastResult().error(), 0) - << initializer.InitializeLastResult().error_message(); - - LogDebug("Successfully initialized GMA."); -} - -void FirebaseGmaTest::SetUpTestSuite() { +void FirebaseUmpTest::SetUpTestSuite() { LogDebug("Initialize Firebase App."); FindFirebaseConfig(FIREBASE_CONFIG_STRING); @@ -234,2372 +99,16 @@ void FirebaseGmaTest::SetUpTestSuite() { #else shared_app_ = ::firebase::App::Create(); #endif // defined(ANDROID) - - InitializeGma(shared_app_); } -void FirebaseGmaTest::TearDownTestSuite() { - // GMA does some of its initialization in the main thread, so if you terminate - // it before initialization has completed, it can cause issues. So wait for - // any pending GMA initialization to be completed before calling terminate. - WaitForCompletion(firebase::gma::InitializeLastResult(), - "gma::InitializeLastResult"); - LogDebug("Shutdown GMA."); - firebase::gma::Terminate(); +void FirebaseUmpTest::TearDownTestSuite() { LogDebug("Shutdown Firebase App."); delete shared_app_; shared_app_ = nullptr; } -FirebaseGmaTest::FirebaseGmaTest() {} - -FirebaseGmaTest::~FirebaseGmaTest() {} - -void FirebaseGmaTest::SetUp() { - TEST_DOES_NOT_REQUIRE_USER_INTERACTION; - ProcessEvents(100); // Flush main thread queue. - FirebaseTest::SetUp(); - - // This example uses ad units that are specially configured to return test ads - // for every request. When using your own ad unit IDs, however, it's important - // to register the device IDs associated with any devices that will be used to - // test the app. This ensures that regardless of the ad unit ID, those - // devices will always receive test ads in compliance with GMA policy. - // - // Device IDs can be obtained by checking the logcat or the Xcode log while - // debugging. They appear as a long string of hex characters. - firebase::gma::RequestConfiguration request_configuration; - request_configuration.test_device_ids = kTestDeviceIDs; - request_configuration.test_device_ids.push_back(GetDebugDeviceId()); - firebase::gma::SetRequestConfiguration(request_configuration); -} - -void FirebaseGmaTest::TearDown() { FirebaseTest::TearDown(); } - -firebase::gma::AdRequest FirebaseGmaTest::GetAdRequest() { - firebase::gma::AdRequest request; - - // Additional keywords to be used in targeting. - for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); - ++keyword_iter) { - request.add_keyword((*keyword_iter).c_str()); - } - - for (auto extras_iter = kGmaAdapterExtras.begin(); - extras_iter != kGmaAdapterExtras.end(); ++extras_iter) { - request.add_extra(kAdNetworkExtrasClassName, extras_iter->first.c_str(), - extras_iter->second.c_str()); - } - - // Content URL - request.set_content_url(kContentUrl); - - // Neighboring Content URLs - request.add_neighboring_content_urls(kNeighboringContentURLs); - - return request; -} - -firebase::Variant FirebaseGmaTest::GetVariantMap() { - firebase::Variant in_key = firebase::Variant::FromMutableString("inner_key"); - firebase::Variant in_val = firebase::Variant::FromMutableString("inner_val"); - firebase::Variant out_key = firebase::Variant::FromMutableString("outer_key"); - - firebase::Variant out_val = firebase::Variant::EmptyMap(); - out_val.map()[in_key] = in_val; - - firebase::Variant variant_map = firebase::Variant::EmptyMap(); - variant_map.map()[out_key] = out_val; - - return variant_map; -} - -FirebaseGmaMinimalTest::FirebaseGmaMinimalTest() {} - -FirebaseGmaMinimalTest::~FirebaseGmaMinimalTest() {} - -FirebaseGmaUITest::FirebaseGmaUITest() {} - -FirebaseGmaUITest::~FirebaseGmaUITest() {} - -void FirebaseGmaUITest::SetUp() { - TEST_REQUIRES_USER_INTERACTION; - FirebaseTest::SetUp(); - // This example uses ad units that are specially configured to return test ads - // for every request. When using your own ad unit IDs, however, it's important - // to register the device IDs associated with any devices that will be used to - // test the app. This ensures that regardless of the ad unit ID, those - // devices will always receive test ads in compliance with GMA policy. - // - // Device IDs can be obtained by checking the logcat or the Xcode log while - // debugging. They appear as a long string of hex characters. - firebase::gma::RequestConfiguration request_configuration; - request_configuration.test_device_ids = kTestDeviceIDs; - request_configuration.test_device_ids.push_back(GetDebugDeviceId()); - firebase::gma::SetRequestConfiguration(request_configuration); -} - -FirebaseGmaPreInitializationTests::FirebaseGmaPreInitializationTests() {} - -FirebaseGmaPreInitializationTests::~FirebaseGmaPreInitializationTests() {} - -void FirebaseGmaPreInitializationTests::SetUp() { FirebaseTest::SetUp(); } - -void FirebaseGmaPreInitializationTests::SetUpTestSuite() { - LogDebug("Initialize Firebase App."); - FindFirebaseConfig(FIREBASE_CONFIG_STRING); -#if defined(ANDROID) - shared_app_ = ::firebase::App::Create(app_framework::GetJniEnv(), - app_framework::GetActivity()); -#else - shared_app_ = ::firebase::App::Create(); -#endif // defined(ANDROID) -} - -// Test cases below. - -TEST_F(FirebaseGmaMinimalTest, TestInitializeGmaWithoutFirebase) { - // Don't initialize mediation in this test so that a later test can still - // verify that mediation has not been initialized. - firebase::gma::DisableMediationInitialization(); - LogDebug("Initializing GMA without a Firebase App."); - firebase::InitResult result; -#if defined(ANDROID) - ::firebase::gma::Initialize(app_framework::GetJniEnv(), - app_framework::GetActivity(), &result); -#else // !defined(ANDROID) - ::firebase::gma::Initialize(&result); -#endif // defined(ANDROID) - EXPECT_EQ(result, ::firebase::kInitResultSuccess); - WaitForCompletion(firebase::gma::InitializeLastResult(), "gma::Initialize"); - LogDebug("Successfully initialized GMA."); - - LogDebug("Shutdown GMA."); - firebase::gma::Terminate(); -} - -TEST_F(FirebaseGmaPreInitializationTests, TestDisableMediationInitialization) { - // Note: This test should be disabled or put in an entirely different test - // binrary if we ever wish to test mediation in this application. - firebase::gma::DisableMediationInitialization(); - - // Ensure that GMA can initialize. - InitializeGma(shared_app_); - auto initialize_future = firebase::gma::InitializeLastResult(); - WaitForCompletion(initialize_future, "gma::Initialize"); - ASSERT_NE(initialize_future.result(), nullptr); - EXPECT_EQ(*initialize_future.result(), - firebase::gma::GetInitializationStatus()); - -#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) - // Check to see that only one Adapter was initialized, the base GMA adapter. - // Note: DisableMediationInitialization is only implemented on iOS. - std::map adapter_status = - firebase::gma::GetInitializationStatus().GetAdapterStatusMap(); - EXPECT_EQ(adapter_status.size(), 1); - EXPECT_THAT( - adapter_status, - Contains( - Pair(kGmaClassName, - Property(&firebase::gma::AdapterStatus::is_initialized, true)))) - << "Expected adapter class '" << kGmaClassName << "' is not loaded."; -#endif -} - -TEST_F(FirebaseGmaTest, TestInitializationStatus) { - // Ensure Initialize()'s result matches GetInitializationStatus(). - auto initialize_future = firebase::gma::InitializeLastResult(); - WaitForCompletion(initialize_future, "gma::Initialize"); - ASSERT_NE(initialize_future.result(), nullptr); - EXPECT_EQ(*initialize_future.result(), - firebase::gma::GetInitializationStatus()); - - for (auto adapter_status : - firebase::gma::GetInitializationStatus().GetAdapterStatusMap()) { - LogDebug("GMA Mediation Adapter '%s' %s (latency %d ms): %s", - adapter_status.first.c_str(), - (adapter_status.second.is_initialized() ? "loaded" : "NOT loaded"), - adapter_status.second.latency(), - adapter_status.second.description().c_str()); - } - - // Confirm that the default Google Mobile Ads SDK class name shows up in the - // list. It should either be is_initialized = true, or description should say - // "Timeout" (this is a special case we are using to deflake this test on - // Android emulator). - EXPECT_THAT( - initialize_future.result()->GetAdapterStatusMap(), - Contains(Pair( - kGmaClassName, - AnyOf(Property(&firebase::gma::AdapterStatus::is_initialized, true), - Property(&firebase::gma::AdapterStatus::description, - HasSubstr("Timeout")))))) - << "Expected adapter class '" << kGmaClassName << "' is not loaded."; -} - -TEST_F(FirebaseGmaPreInitializationTests, TestDisableSDKCrashReporting) { - // We can't test to see if this method successfully reconfigures crash - // reporting, but we're still calling it as a sanity check and to ensure - // the symbol exists in the library. - firebase::gma::DisableSDKCrashReporting(); -} - -TEST_F(FirebaseGmaTest, TestSetAppKeyEnabled) { - // We can't test to see if this method successfully enables/disables - // the app key, but we're still calling it as a sanity check and to - // ensure the symbol exists in the library. - firebase::gma::SetIsSameAppKeyEnabled(true); -} - -TEST_F(FirebaseGmaTest, TestGetAdRequest) { GetAdRequest(); } - -TEST_F(FirebaseGmaTest, TestGetVariantMap) { GetVariantMap(); } - -TEST_F(FirebaseGmaTest, TestGetAdRequestValues) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::AdRequest request = GetAdRequest(); - - // Content URL. - EXPECT_TRUE(request.content_url() == std::string(kContentUrl)); - - // Extras. - std::map > configured_extras = - request.extras(); - - EXPECT_EQ(configured_extras.size(), 1); - for (auto extras_name_iter = configured_extras.begin(); - extras_name_iter != configured_extras.end(); ++extras_name_iter) { - // Confirm class name. - const std::string class_name = extras_name_iter->first; - EXPECT_EQ(class_name, kAdNetworkExtrasClassName); - - // Grab the extras. - const std::map& configured_extras = - extras_name_iter->second; - EXPECT_EQ(configured_extras.size(), kGmaAdapterExtras.size()); - - // Check the extra key value pairs. - for (auto constant_extras_iter = kGmaAdapterExtras.begin(); - constant_extras_iter != kGmaAdapterExtras.end(); - ++constant_extras_iter) { - // Ensure the configured value matches the constant for the same key. - EXPECT_EQ(configured_extras.at(constant_extras_iter->first), - constant_extras_iter->second); - } - } - - const std::unordered_set configured_keywords = - request.keywords(); - EXPECT_EQ(configured_keywords.size(), kKeywords.size()); - for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); - ++keyword_iter) { - EXPECT_TRUE(configured_keywords.find(*keyword_iter) != - configured_keywords.end()); - } - - const std::unordered_set configured_neighboring_content_urls = - request.neighboring_content_urls(); - EXPECT_EQ(configured_neighboring_content_urls.size(), - kNeighboringContentURLs.size()); - for (auto url_iter = kNeighboringContentURLs.begin(); - url_iter != kNeighboringContentURLs.end(); ++url_iter) { - EXPECT_TRUE(configured_neighboring_content_urls.find(*url_iter) != - configured_neighboring_content_urls.end()); - } -} - -// A listener to detect when the AdInspector has been closed. Additionally, -// checks for errors when opening the AdInspector while it's already open. -class TestAdInspectorClosedListener - : public firebase::gma::AdInspectorClosedListener { - public: - TestAdInspectorClosedListener() - : num_closed_events_(0), num_successful_results_(0) {} - - // Called when the user clicked the ad. - void OnAdInspectorClosed(const firebase::gma::AdResult& ad_result) override { - ++num_closed_events_; - if (ad_result.is_successful()) { - ++num_successful_results_; - } else { -#if defined(ANDROID) - EXPECT_EQ(ad_result.ad_error().code(), - firebase::gma::kAdErrorCodeInsepctorAlreadyOpen); - EXPECT_STREQ(ad_result.ad_error().message().c_str(), - "Ad inspector cannot be opened because it is already open."); -#else - // The iOS GMA SDK returns internal errors for all AdInspector failures. - EXPECT_EQ(ad_result.ad_error().code(), - firebase::gma::kAdErrorCodeInternalError); - EXPECT_STREQ(ad_result.ad_error().message().c_str(), - "Ad Inspector cannot be opened because it is already open."); -#endif - } - } - - uint8_t num_closed_events() const { return num_closed_events_; } - uint8_t num_successful_results() const { return num_successful_results_; } - - private: - uint8_t num_closed_events_; - uint8_t num_successful_results_; -}; - -// This is for manual test only -// Ensure we can open the AdInspector and listen to its events. -TEST_F(FirebaseGmaTest, TestAdInspector) { - TEST_REQUIRES_USER_INTERACTION; - TestAdInspectorClosedListener listener; - - firebase::gma::OpenAdInspector(app_framework::GetWindowController(), - &listener); - - // Call OpenAdInspector, even on Desktop (above), to ensure the stub linked - // correctly. However, the rest of the testing is mobile-only beahvior. - SKIP_TEST_ON_DESKTOP; - - // Open the inspector a second time to generate a - // kAdErrorCodeInsepctorAlreadyOpen result. - app_framework::ProcessEvents(2000); - - firebase::gma::OpenAdInspector(app_framework::GetWindowController(), - &listener); - - while (listener.num_closed_events() < 2) { - app_framework::ProcessEvents(2000); - } - - EXPECT_EQ(listener.num_successful_results(), 1); -} - -// A simple listener to help test changes to a AdViews. -class TestBoundingBoxListener - : public firebase::gma::AdViewBoundingBoxListener { - public: - void OnBoundingBoxChanged(firebase::gma::AdView* ad_view, - firebase::gma::BoundingBox box) override { - bounding_box_changes_.push_back(box); - } - std::vector bounding_box_changes_; -}; - -// A simple listener to help test changes an Ad. -class TestAdListener : public firebase::gma::AdListener { - public: - TestAdListener() - : num_on_ad_clicked_(0), - num_on_ad_closed_(0), - num_on_ad_impression_(0), - num_on_ad_opened_(0) {} - - void OnAdClicked() override { num_on_ad_clicked_++; } - void OnAdClosed() override { num_on_ad_closed_++; } - void OnAdImpression() override { num_on_ad_impression_++; } - void OnAdOpened() override { num_on_ad_opened_++; } - - int num_on_ad_clicked_; - int num_on_ad_closed_; - int num_on_ad_impression_; - int num_on_ad_opened_; -}; - -// A simple listener track FullScreen presentation changes. -class TestFullScreenContentListener - : public firebase::gma::FullScreenContentListener { - public: - TestFullScreenContentListener() - : num_on_ad_clicked_(0), - num_on_ad_dismissed_full_screen_content_(0), - num_on_ad_failed_to_show_full_screen_content_(0), - num_on_ad_impression_(0), - num_on_ad_showed_full_screen_content_(0) {} - - int num_ad_clicked() const { return num_on_ad_clicked_; } - int num_ad_dismissed() const { - return num_on_ad_dismissed_full_screen_content_; - } - int num_ad_failed_to_show_content() const { - return num_on_ad_failed_to_show_full_screen_content_; - } - int num_ad_impressions() const { return num_on_ad_impression_; } - int num_ad_showed_content() const { - return num_on_ad_showed_full_screen_content_; - } - - void OnAdClicked() override { num_on_ad_clicked_++; } - - void OnAdDismissedFullScreenContent() override { - num_on_ad_dismissed_full_screen_content_++; - } - - void OnAdFailedToShowFullScreenContent( - const firebase::gma::AdError& ad_error) override { - num_on_ad_failed_to_show_full_screen_content_++; - failure_codes_.push_back(ad_error.code()); - } - - void OnAdImpression() override { num_on_ad_impression_++; } - - void OnAdShowedFullScreenContent() override { - num_on_ad_showed_full_screen_content_++; - } - - const std::vector failure_codes() const { - return failure_codes_; - } - - private: - int num_on_ad_clicked_; - int num_on_ad_dismissed_full_screen_content_; - int num_on_ad_failed_to_show_full_screen_content_; - int num_on_ad_impression_; - int num_on_ad_showed_full_screen_content_; - - std::vector failure_codes_; -}; - -// A simple listener track UserEarnedReward events. -class TestUserEarnedRewardListener - : public firebase::gma::UserEarnedRewardListener { - public: - TestUserEarnedRewardListener() : num_on_user_earned_reward_(0) {} - - int num_earned_rewards() const { return num_on_user_earned_reward_; } - - void OnUserEarnedReward(const firebase::gma::AdReward& reward) override { - ++num_on_user_earned_reward_; - EXPECT_EQ(reward.type(), "coins"); - EXPECT_EQ(reward.amount(), 10); - } - - private: - int num_on_user_earned_reward_; -}; - -// A simple listener track ad pay events. -class TestPaidEventListener : public firebase::gma::PaidEventListener { - public: - TestPaidEventListener() : num_on_paid_event_(0) {} - - int num_paid_events() const { return num_on_paid_event_; } - - void OnPaidEvent(const firebase::gma::AdValue& value) override { - ++num_on_paid_event_; - // These are the values for GMA test ads. If they change then we should - // alter the test to match the new expected values. - EXPECT_EQ(value.currency_code(), "USD"); - EXPECT_EQ(value.value_micros(), 0); - } - int num_on_paid_event_; -}; - -TEST_F(FirebaseGmaTest, TestAdSize) { - uint32_t kWidth = 50; - uint32_t kHeight = 10; - - using firebase::gma::AdSize; - - const AdSize adaptive_landscape = - AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(kWidth); - EXPECT_EQ(adaptive_landscape.width(), kWidth); - EXPECT_EQ(adaptive_landscape.height(), 0); - EXPECT_EQ(adaptive_landscape.type(), AdSize::kTypeAnchoredAdaptive); - EXPECT_EQ(adaptive_landscape.orientation(), AdSize::kOrientationLandscape); - - const AdSize adaptive_portrait = - AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(kWidth); - EXPECT_EQ(adaptive_portrait.width(), kWidth); - EXPECT_EQ(adaptive_portrait.height(), 0); - EXPECT_EQ(adaptive_portrait.type(), AdSize::kTypeAnchoredAdaptive); - EXPECT_EQ(adaptive_portrait.orientation(), AdSize::kOrientationPortrait); - - EXPECT_FALSE(adaptive_portrait == adaptive_landscape); - EXPECT_TRUE(adaptive_portrait != adaptive_landscape); - - const firebase::gma::AdSize adaptive_current = - AdSize::GetCurrentOrientationAnchoredAdaptiveBannerAdSize(kWidth); - EXPECT_EQ(adaptive_current.width(), kWidth); - EXPECT_EQ(adaptive_current.height(), 0); - EXPECT_EQ(adaptive_current.type(), AdSize::kTypeAnchoredAdaptive); - EXPECT_EQ(adaptive_current.orientation(), AdSize::kOrientationCurrent); - - const firebase::gma::AdSize custom_ad_size(kWidth, kHeight); - EXPECT_EQ(custom_ad_size.width(), kWidth); - EXPECT_EQ(custom_ad_size.height(), kHeight); - EXPECT_EQ(custom_ad_size.type(), AdSize::kTypeStandard); - EXPECT_EQ(custom_ad_size.orientation(), AdSize::kOrientationCurrent); - - const AdSize custom_ad_size_2(kWidth, kHeight); - EXPECT_TRUE(custom_ad_size == custom_ad_size_2); - EXPECT_FALSE(custom_ad_size != custom_ad_size_2); - - const AdSize banner = AdSize::kBanner; - EXPECT_EQ(banner.width(), 320); - EXPECT_EQ(banner.height(), 50); - EXPECT_EQ(banner.type(), AdSize::kTypeStandard); - EXPECT_EQ(banner.orientation(), AdSize::kOrientationCurrent); - - const AdSize fullbanner = AdSize::kFullBanner; - EXPECT_EQ(fullbanner.width(), 468); - EXPECT_EQ(fullbanner.height(), 60); - EXPECT_EQ(fullbanner.type(), AdSize::kTypeStandard); - EXPECT_EQ(fullbanner.orientation(), AdSize::kOrientationCurrent); - - const AdSize leaderboard = AdSize::kLeaderboard; - EXPECT_EQ(leaderboard.width(), 728); - EXPECT_EQ(leaderboard.height(), 90); - EXPECT_EQ(leaderboard.type(), AdSize::kTypeStandard); - EXPECT_EQ(leaderboard.orientation(), AdSize::kOrientationCurrent); - - const AdSize medium_rectangle = AdSize::kMediumRectangle; - EXPECT_EQ(medium_rectangle.width(), 300); - EXPECT_EQ(medium_rectangle.height(), 250); - EXPECT_EQ(medium_rectangle.type(), AdSize::kTypeStandard); - EXPECT_EQ(medium_rectangle.orientation(), AdSize::kOrientationCurrent); -} - -TEST_F(FirebaseGmaTest, TestRequestConfigurationSetGetEmptyConfig) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::RequestConfiguration set_configuration; - firebase::gma::SetRequestConfiguration(set_configuration); - firebase::gma::RequestConfiguration retrieved_configuration = - firebase::gma::GetRequestConfiguration(); - - EXPECT_EQ( - retrieved_configuration.max_ad_content_rating, - firebase::gma::RequestConfiguration::kMaxAdContentRatingUnspecified); - EXPECT_EQ( - retrieved_configuration.tag_for_child_directed_treatment, - firebase::gma::RequestConfiguration::kChildDirectedTreatmentUnspecified); - EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, - firebase::gma::RequestConfiguration::kUnderAgeOfConsentUnspecified); - EXPECT_EQ(retrieved_configuration.test_device_ids.size(), 0); -} - -TEST_F(FirebaseGmaTest, TestRequestConfigurationSetGet) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::RequestConfiguration set_configuration; - set_configuration.max_ad_content_rating = - firebase::gma::RequestConfiguration::kMaxAdContentRatingPG; - set_configuration.tag_for_child_directed_treatment = - firebase::gma::RequestConfiguration::kChildDirectedTreatmentTrue; - set_configuration.tag_for_under_age_of_consent = - firebase::gma::RequestConfiguration::kUnderAgeOfConsentFalse; - set_configuration.test_device_ids.push_back("1"); - set_configuration.test_device_ids.push_back("2"); - set_configuration.test_device_ids.push_back("3"); - firebase::gma::SetRequestConfiguration(set_configuration); - - firebase::gma::RequestConfiguration retrieved_configuration = - firebase::gma::GetRequestConfiguration(); - - EXPECT_EQ(retrieved_configuration.max_ad_content_rating, - firebase::gma::RequestConfiguration::kMaxAdContentRatingPG); - -#if defined(ANDROID) - EXPECT_EQ(retrieved_configuration.tag_for_child_directed_treatment, - firebase::gma::RequestConfiguration::kChildDirectedTreatmentTrue); - EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, - firebase::gma::RequestConfiguration::kUnderAgeOfConsentFalse); -#else // iOS - // iOS doesn't allow for the querying of these values. - EXPECT_EQ( - retrieved_configuration.tag_for_child_directed_treatment, - firebase::gma::RequestConfiguration::kChildDirectedTreatmentUnspecified); - EXPECT_EQ(retrieved_configuration.tag_for_under_age_of_consent, - firebase::gma::RequestConfiguration::kUnderAgeOfConsentUnspecified); -#endif - - EXPECT_EQ(retrieved_configuration.test_device_ids.size(), 3); - EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), - retrieved_configuration.test_device_ids.end(), "1")); - EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), - retrieved_configuration.test_device_ids.end(), "2")); - EXPECT_TRUE(std::count(retrieved_configuration.test_device_ids.begin(), - retrieved_configuration.test_device_ids.end(), "3")); -} - -// Simple Load Tests as a sanity check. These don't show the ad, just -// ensure that we can load them before diving into the interactive tests. -TEST_F(FirebaseGmaTest, TestAdViewLoadAd) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - firebase::Future load_ad_future; - const firebase::gma::AdResult* result_ptr = nullptr; - - load_ad_future = ad_view->LoadAd(GetAdRequest()); - WaitForCompletion(load_ad_future, "LoadAd"); - - result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - - EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); - EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); - EXPECT_EQ(ad_view->ad_size().type(), firebase::gma::AdSize::kTypeStandard); - - load_ad_future.Release(); - WaitForCompletion(ad_view->Destroy(), "Destroy"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdLoad) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::InterstitialAd* interstitial = - new firebase::gma::InterstitialAd(); - - WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the InterstitialAd is initialized, load an ad. - firebase::Future load_ad_future = - interstitial->LoadAd(kInterstitialAdUnit, GetAdRequest()); - - // This test behaves differently if it's running in UI mode - // (manually on a device) or in non-UI mode (via automated tests). - if (ShouldRunUITests()) { - // Run in manual mode: fail if any error occurs. - WaitForCompletion(load_ad_future, "LoadAd"); - } else { - // Run in automated test mode: don't fail if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - } - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - } - - load_ad_future.Release(); - delete interstitial; -} - -TEST_F(FirebaseGmaTest, TestRewardedAdLoad) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the RewardedAd is initialized, load an ad. - firebase::Future load_ad_future = - rewarded->LoadAd(kRewardedAdUnit, GetAdRequest()); - - // This test behaves differently if it's running in UI mode - // (manually on a device) or in non-UI mode (via automated tests). - if (ShouldRunUITests()) { - // Run in manual mode: fail if any error occurs. - WaitForCompletion(load_ad_future, "LoadAd"); - } else { - // Run in automated test mode: don't fail if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - } - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - // In UI mode, or in non-UI mode if a NoFill error didn't occur, check that - // the ad loaded correctly. - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - } - load_ad_future.Release(); - delete rewarded; -} - -TEST_F(FirebaseGmaTest, TestNativeAdLoad) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the NativeAd is initialized, load an ad. - firebase::Future load_ad_future = - native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); - - // Don't fail loadAd, if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - - // Check image assets. - // Native ads usually contain only one large image asset. - // Check the validity of the first asset from the vector. - EXPECT_FALSE(native_ad->images().empty()); - EXPECT_FALSE(native_ad->images().at(0).image_uri().empty()); - EXPECT_GT(native_ad->images().at(0).scale(), 0); - - // Try loading large image asset. - firebase::Future load_image_future = - native_ad->images().at(0).LoadImage(); - WaitForCompletion(load_image_future, "LoadImage"); - const firebase::gma::ImageResult* img_result_ptr = - load_image_future.result(); - ASSERT_NE(img_result_ptr, nullptr); - EXPECT_TRUE(img_result_ptr->is_successful()); - EXPECT_GT(img_result_ptr->image().size(), 0); - - load_image_future.Release(); - } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { - LogWarning("LoadAd returned NoFill in TestNativeAdLoad"); - } - - load_ad_future.Release(); - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestCreateQueryInfo) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); - - WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - firebase::gma::AdRequest request = GetAdRequest(); - // Set the requester type to 8. QueryInfo gets generated without a - // query_info_type set, but throws a warning that it is missing. - request.add_extra(kAdNetworkExtrasClassName, "query_info_type", - "requester_type_8"); - // When the QueryInfo is initialized, generate a query info string. - firebase::Future create_query_info_future = - query_info->CreateQueryInfo(firebase::gma::kAdFormatNative, request); - - WaitForCompletion(create_query_info_future, "CreateQueryInfo"); - - if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::QueryInfoResult* result_ptr = - create_query_info_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->query_info().empty()); - } - - create_query_info_future.Release(); - delete query_info; -} - -TEST_F(FirebaseGmaTest, TestCreateQueryInfoWithAdUnit) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::QueryInfo* query_info = new firebase::gma::QueryInfo(); - - WaitForCompletion(query_info->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - firebase::gma::AdRequest request = GetAdRequest(); - // Set the requester type to 8. QueryInfo gets generated without a - // query_info_type set, but throws a warning that it is missing. - request.add_extra(kAdNetworkExtrasClassName, "query_info_type", - "requester_type_8"); - // When the QueryInfo is initialized, generate a query info string. - // Providing a bad/empty ad unit does not affect the query info generation. - firebase::Future create_query_info_future = - query_info->CreateQueryInfoWithAdUnit(firebase::gma::kAdFormatNative, - request, kNativeAdUnit); - - WaitForCompletion(create_query_info_future, "CreateQueryInfoWithAdUnit"); - - if (create_query_info_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::QueryInfoResult* result_ptr = - create_query_info_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->query_info().empty()); - } - - create_query_info_future.Release(); - delete query_info; -} - -// Interactive test section. These have been placed up front so that the -// tester doesn't get bored waiting for them. -TEST_F(FirebaseGmaUITest, TestAdViewAdOpenedAdClosed) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - // Set the listener. - TestAdListener ad_listener; - ad_view->SetAdListener(&ad_listener); - - TestPaidEventListener paid_event_listener; - ad_view->SetPaidEventListener(&paid_event_listener); - - // Load the AdView ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - ad_view->LoadAd(request); - WaitForCompletion(load_ad_future, "LoadAd"); - - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - WaitForCompletion(ad_view->Show(), "Show 0"); - - // Ad Events differ per platform. See the following for more info: - // https://www.googblogs.com/google-mobile-ads-sdk-a-note-on-ad-click-events/ - // and https://groups.google.com/g/google-admob-ads-sdk/c/lzdt5szxSVU -#if defined(ANDROID) - LogDebug("Click the Ad, and then close the ad to continue"); - - while (ad_listener.num_on_ad_opened_ == 0) { - app_framework::ProcessEvents(1000); - } - - while (ad_listener.num_on_ad_closed_ == 0) { - app_framework::ProcessEvents(1000); - } - - // Ensure all of the expected events were triggered on Android. - EXPECT_EQ(ad_listener.num_on_ad_clicked_, 1); - EXPECT_EQ(ad_listener.num_on_ad_impression_, 1); - EXPECT_EQ(ad_listener.num_on_ad_opened_, 1); - EXPECT_EQ(ad_listener.num_on_ad_closed_, 1); - EXPECT_EQ(paid_event_listener.num_paid_events(), 1); -#else - LogDebug("Click the Ad, and then close the ad to continue"); - - while (ad_listener.num_on_ad_clicked_ == 0) { - app_framework::ProcessEvents(1000); - } - - LogDebug("Waiting for a moment to ensure all callbacks are recorded."); - app_framework::ProcessEvents(2000); - - // Ensure all of the expected events were triggered on iOS. - EXPECT_EQ(ad_listener.num_on_ad_clicked_, 1); - EXPECT_EQ(ad_listener.num_on_ad_impression_, 1); - EXPECT_EQ(paid_event_listener.num_paid_events(), 1); - EXPECT_EQ(ad_listener.num_on_ad_opened_, 0); - EXPECT_EQ(ad_listener.num_on_ad_closed_, 0); -#endif - } - - load_ad_future.Release(); - ad_view->SetAdListener(nullptr); - ad_view->SetPaidEventListener(nullptr); - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; -} - -TEST_F(FirebaseGmaUITest, TestInterstitialAdLoadAndShow) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::InterstitialAd* interstitial = - new firebase::gma::InterstitialAd(); - - WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - TestFullScreenContentListener content_listener; - interstitial->SetFullScreenContentListener(&content_listener); - - TestPaidEventListener paid_event_listener; - interstitial->SetPaidEventListener(&paid_event_listener); - - // When the InterstitialAd is initialized, load an ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - interstitial->LoadAd(kInterstitialAdUnit, request); - WaitForCompletion(load_ad_future, "LoadAd"); - - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - WaitForCompletion(interstitial->Show(), "Show"); - - LogDebug("Click the Ad, and then return to the app to continue"); - - while (content_listener.num_ad_dismissed() == 0) { - app_framework::ProcessEvents(1000); - } - - LogDebug("Waiting for a moment to ensure all callbacks are recorded."); - app_framework::ProcessEvents(2000); - - EXPECT_EQ(content_listener.num_ad_clicked(), 1); - EXPECT_EQ(content_listener.num_ad_showed_content(), 1); - EXPECT_EQ(content_listener.num_ad_impressions(), 1); - EXPECT_EQ(content_listener.num_ad_failed_to_show_content(), 0); - EXPECT_EQ(content_listener.num_ad_dismissed(), 1); - EXPECT_EQ(paid_event_listener.num_paid_events(), 1); - -#if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) - // Show the Ad again. Note: Android's Interstitial ads fail silently - // when attempting to show the ad twice. - LogDebug("Attempting to show ad again, checking for correct error result."); - WaitForCompletion(interstitial->Show(), "Show"); - app_framework::ProcessEvents(5000); - EXPECT_THAT(content_listener.failure_codes(), - ElementsAre(firebase::gma::kAdErrorCodeAdAlreadyUsed)); -#endif // TARGET_OS_IPHONE - } - - load_ad_future.Release(); - interstitial->SetFullScreenContentListener(nullptr); - interstitial->SetPaidEventListener(nullptr); - - delete interstitial; -} - -TEST_F(FirebaseGmaUITest, TestRewardedAdLoadAndShow) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - // TODO(@drsanta): remove when GMA whitelists CI devices. - TEST_REQUIRES_USER_INTERACTION_ON_IOS; - - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - TestFullScreenContentListener content_listener; - rewarded->SetFullScreenContentListener(&content_listener); - - TestPaidEventListener paid_event_listener; - rewarded->SetPaidEventListener(&paid_event_listener); - - // When the RewardedAd is initialized, load an ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - rewarded->LoadAd(kRewardedAdUnit, request); - WaitForCompletion(load_ad_future, "LoadAd"); - - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - firebase::gma::RewardedAd::ServerSideVerificationOptions options; - // We cannot programmatically verify that the GMA phone SDKs marshal - // these values properly (there are no get methods). At least invoke the - // method to ensure least we can set them without any exceptions occurring. - options.custom_data = "custom data"; - options.user_id = "123456"; - rewarded->SetServerSideVerificationOptions(options); - - TestUserEarnedRewardListener earned_reward_listener; - WaitForCompletion(rewarded->Show(&earned_reward_listener), "Show"); - - LogDebug( - "Wait for the Ad to finish playing, click the ad, return to the ad, " - "then close the ad to continue."); - - while (content_listener.num_ad_dismissed() == 0) { - app_framework::ProcessEvents(1000); - } - - LogDebug("Waiting for a moment to ensure all callbacks are recorded."); - app_framework::ProcessEvents(2000); - - // If not running the UI test in CI (running manually), keep this check. - // Else running the UI test in CI, skip this check. - if (!ShouldRunUITests()) { - EXPECT_EQ(content_listener.num_ad_clicked(), 1); - } - EXPECT_EQ(content_listener.num_ad_showed_content(), 1); - EXPECT_EQ(content_listener.num_ad_impressions(), 1); - EXPECT_EQ(content_listener.num_ad_dismissed(), 1); - EXPECT_EQ(content_listener.num_ad_failed_to_show_content(), 0); - EXPECT_EQ(earned_reward_listener.num_earned_rewards(), 1); - EXPECT_EQ(paid_event_listener.num_paid_events(), 1); - - // Show the Ad again - LogDebug("Attempting to show ad again, checking for correct error result."); - WaitForCompletion(rewarded->Show(&earned_reward_listener), "Show"); - app_framework::ProcessEvents(2000); - EXPECT_THAT(content_listener.failure_codes(), - testing::ElementsAre(firebase::gma::kAdErrorCodeAdAlreadyUsed)); - } - - load_ad_future.Release(); - rewarded->SetFullScreenContentListener(nullptr); - rewarded->SetPaidEventListener(nullptr); - - delete rewarded; -} - -// Other AdView Tests - -TEST_F(FirebaseGmaTest, TestAdViewLoadAdEmptyAdRequest) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - firebase::gma::AdRequest request; - firebase::Future load_ad_future; - const firebase::gma::AdResult* result_ptr = nullptr; - - load_ad_future = ad_view->LoadAd(request); - WaitForCompletion(load_ad_future, "LoadAd"); - result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - - EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); - EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); - EXPECT_EQ(ad_view->ad_size().type(), firebase::gma::AdSize::kTypeStandard); - - load_ad_future.Release(); - WaitForCompletion(ad_view->Destroy(), "Destroy"); - - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewLoadAdAnchorAdaptiveAd) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - using firebase::gma::AdSize; - AdSize banner_ad_size = - AdSize::GetCurrentOrientationAnchoredAdaptiveBannerAdSize(kBannerWidth); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); - - const AdSize ad_size = ad_view->ad_size(); - EXPECT_EQ(ad_size.width(), kBannerWidth); - EXPECT_NE(ad_size.height(), 0); - EXPECT_EQ(ad_size.type(), AdSize::kTypeAnchoredAdaptive); - EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); - WaitForCompletion(ad_view->Destroy(), "Destroy"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewLoadAdInlineAdaptiveAd) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - using firebase::gma::AdSize; - - using firebase::gma::AdSize; - AdSize banner_ad_size = - AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(kBannerWidth); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); - - const AdSize ad_size = ad_view->ad_size(); - EXPECT_EQ(ad_size.width(), kBannerWidth); - EXPECT_NE(ad_size.height(), 0); - EXPECT_EQ(ad_size.type(), AdSize::kTypeInlineAdaptive); - EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); - WaitForCompletion(ad_view->Destroy(), "Destroy"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewLoadAdGetInlineAdaptiveBannerMaxHeight) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - using firebase::gma::AdSize; - AdSize banner_ad_size = - AdSize::GetInlineAdaptiveBannerAdSize(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); - - const AdSize ad_size = ad_view->ad_size(); - EXPECT_EQ(ad_size.width(), kBannerWidth); - EXPECT_NE(ad_size.height(), 0); - EXPECT_TRUE(ad_size.height() <= kBannerHeight); - EXPECT_EQ(ad_size.type(), AdSize::kTypeInlineAdaptive); - EXPECT_EQ(ad_size.orientation(), AdSize::kOrientationCurrent); - WaitForCompletion(ad_view->Destroy(), "Destroy"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewLoadAdDestroyNotCalled) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewAdSizeCompareOp) { - using firebase::gma::AdSize; - EXPECT_TRUE(AdSize(50, 100) == AdSize(50, 100)); - EXPECT_TRUE(AdSize(100, 50) == AdSize(100, 50)); - EXPECT_FALSE(AdSize(50, 100) == AdSize(100, 50)); - EXPECT_FALSE(AdSize(10, 10) == AdSize(50, 50)); - - EXPECT_FALSE(AdSize(50, 100) != AdSize(50, 100)); - EXPECT_FALSE(AdSize(100, 50) != AdSize(100, 50)); - EXPECT_TRUE(AdSize(50, 100) != AdSize(100, 50)); - EXPECT_TRUE(AdSize(10, 10) != AdSize(50, 50)); - - EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == - AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100)); - EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != - AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100)); - - EXPECT_TRUE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) == - AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); - EXPECT_FALSE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) != - AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); - - EXPECT_TRUE(AdSize::GetInlineAdaptiveBannerAdSize(100, 50) == - AdSize::GetInlineAdaptiveBannerAdSize(100, 50)); - EXPECT_FALSE(AdSize::GetInlineAdaptiveBannerAdSize(100, 50) != - AdSize::GetInlineAdaptiveBannerAdSize(100, 50)); - - EXPECT_TRUE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) == - AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); - EXPECT_FALSE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) != - AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); - - EXPECT_TRUE(AdSize::GetPortraitInlineAdaptiveBannerAdSize(100) == - AdSize::GetPortraitInlineAdaptiveBannerAdSize(100)); - EXPECT_TRUE(AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100) == - AdSize::GetLandscapeInlineAdaptiveBannerAdSize(100)); - EXPECT_TRUE(AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(100) == - AdSize::GetCurrentOrientationInlineAdaptiveBannerAdSize(100)); - - EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == - AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); - EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != - AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100)); - - EXPECT_FALSE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) == - AdSize(100, 100)); - EXPECT_TRUE(AdSize::GetLandscapeAnchoredAdaptiveBannerAdSize(100) != - AdSize(100, 100)); - - EXPECT_FALSE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) == - AdSize(100, 100)); - EXPECT_TRUE(AdSize::GetPortraitAnchoredAdaptiveBannerAdSize(100) != - AdSize(100, 100)); -} - -TEST_F(FirebaseGmaTest, TestAdViewDestroyBeforeInitialization) { - SKIP_TEST_ON_DESKTOP; - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); -} - -TEST_F(FirebaseGmaTest, TestAdViewAdSizeBeforeInitialization) { - SKIP_TEST_ON_DESKTOP; - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - const firebase::gma::AdSize& ad_size = firebase::gma::AdSize(0, 0); - EXPECT_TRUE(ad_view->ad_size() == ad_size); - - WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); -} - -TEST_F(FirebaseGmaTest, TestAdView) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - EXPECT_TRUE(ad_view->ad_size() == banner_ad_size); - - // Set the listener. - TestBoundingBoxListener bounding_box_listener; - ad_view->SetBoundingBoxListener(&bounding_box_listener); - PauseForVisualInspectionAndCallbacks(); - - int expected_num_bounding_box_changes = 0; - EXPECT_EQ(expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // Load the AdView ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - ad_view->LoadAd(request); - WaitForCompletion(load_ad_future, "LoadAd"); - - const bool ad_loaded = - load_ad_future.error() == firebase::gma::kAdErrorCodeNone; - - // Suppress the extensive testing below if the ad failed to load. - if (ad_loaded) { - EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); - EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); - EXPECT_EQ(expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - // Make the AdView visible. - WaitForCompletion(ad_view->Show(), "Show 0"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // Move to each of the six pre-defined positions. - WaitForCompletion(ad_view->SetPosition(firebase::gma::AdView::kPositionTop), - "SetPosition(Top)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionTop); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion( - ad_view->SetPosition(firebase::gma::AdView::kPositionTopLeft), - "SetPosition(TopLeft)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionTopLeft); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion( - ad_view->SetPosition(firebase::gma::AdView::kPositionTopRight), - "SetPosition(TopRight)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionTopRight); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion( - ad_view->SetPosition(firebase::gma::AdView::kPositionBottom), - "SetPosition(Bottom)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionBottom); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion( - ad_view->SetPosition(firebase::gma::AdView::kPositionBottomLeft), - "SetPosition(BottomLeft)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionBottomLeft); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion( - ad_view->SetPosition(firebase::gma::AdView::kPositionBottomRight), - "SetPosition(BottomRight)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionBottomRight); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // Move to some coordinates. - WaitForCompletion(ad_view->SetPosition(100, 300), "SetPosition(x0, y0)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionUndefined); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion(ad_view->SetPosition(100, 400), "SetPosition(x1, y1)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionUndefined); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // Try hiding and showing the AdView. - WaitForCompletion(ad_view->Hide(), "Hide 1"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion(ad_view->Show(), "Show 1"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // Move again after hiding/showing. - WaitForCompletion(ad_view->SetPosition(100, 300), "SetPosition(x2, y2)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionUndefined); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion(ad_view->SetPosition(100, 400), "SetPosition(x3, y3)"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(ad_view->bounding_box().position, - firebase::gma::AdView::kPositionUndefined); - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - WaitForCompletion(ad_view->Hide(), "Hide 2"); - PauseForVisualInspectionAndCallbacks(); - EXPECT_EQ(expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - LogDebug("Waiting for a moment to ensure all callbacks are recorded."); - app_framework::ProcessEvents(2000); - } - - // Clean up the ad object. - load_ad_future.Release(); - WaitForCompletion(ad_view->Destroy(), "Destroy AdView"); - ad_view->SetBoundingBoxListener(nullptr); - delete ad_view; - - PauseForVisualInspectionAndCallbacks(); - - if (ad_loaded) { - // If the ad was show, do the final bounding box checks after the ad has - // been destroyed. -#if defined(ANDROID) || TARGET_OS_IPHONE - EXPECT_EQ(++expected_num_bounding_box_changes, - bounding_box_listener.bounding_box_changes_.size()); - - // As an extra check, all bounding boxes except the last should have the - // same size aspect ratio that we requested. For example if you requested a - // 320x50 banner, you can get one with the size 960x150. Use EXPECT_NEAR - // because the calculation can have a small bit of error. - double kAspectRatioAllowedError = 0.05; // Allow about 5% of error. - double expected_aspect_ratio = - static_cast(kBannerWidth) / static_cast(kBannerHeight); - for (int i = 0; i < bounding_box_listener.bounding_box_changes_.size() - 1; - ++i) { - double actual_aspect_ratio = - static_cast( - bounding_box_listener.bounding_box_changes_[i].width) / - static_cast( - bounding_box_listener.bounding_box_changes_[i].height); - EXPECT_NEAR(actual_aspect_ratio, expected_aspect_ratio, - kAspectRatioAllowedError) - << "AdView size " - << bounding_box_listener.bounding_box_changes_[i].width << "x" - << bounding_box_listener.bounding_box_changes_[i].height - << " does not have the same aspect ratio as requested size " - << kBannerWidth << "x" << kBannerHeight << "."; - } - - // And finally, the last bounding box change, when the AdView is deleted, - // should have invalid values (-1,-1, -1, -1). - EXPECT_TRUE( - bounding_box_listener.bounding_box_changes_.back().x == -1 && - bounding_box_listener.bounding_box_changes_.back().y == -1 && - bounding_box_listener.bounding_box_changes_.back().width == -1 && - bounding_box_listener.bounding_box_changes_.back().height == -1); -#endif // defined(ANDROID) || TARGET_OS_IPHONE - } -} - -TEST_F(FirebaseGmaTest, TestAdViewErrorNotInitialized) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - - WaitForCompletion(ad_view->LoadAd(GetAdRequest()), "LoadAd", - firebase::gma::kAdErrorCodeUninitialized); - - firebase::gma::AdView::Position position; - WaitForCompletion(ad_view->SetPosition(position), "SetPosition(position)", - firebase::gma::kAdErrorCodeUninitialized); - - WaitForCompletion(ad_view->SetPosition(0, 0), "SetPosition(x,y)", - firebase::gma::kAdErrorCodeUninitialized); - - WaitForCompletion(ad_view->Hide(), "Hide", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(ad_view->Show(), "Show", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(ad_view->Pause(), "Pause", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(ad_view->Resume(), "Resume", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewErrorAlreadyInitialized) { - SKIP_TEST_ON_DESKTOP; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - { - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - firebase::Future first_initialize = ad_view->Initialize( - app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); - firebase::Future second_initialize = ad_view->Initialize( - app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); - - WaitForCompletion(first_initialize, "First Initialize 1"); - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - - first_initialize.Release(); - second_initialize.Release(); - WaitForCompletion(ad_view->Destroy(), "Destroy AdView 1"); - delete ad_view; - } - - // Reverse the order of the completion waits. - { - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - firebase::Future first_initialize = ad_view->Initialize( - app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); - firebase::Future second_initialize = ad_view->Initialize( - app_framework::GetWindowContext(), kBannerAdUnit, banner_ad_size); - - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - WaitForCompletion(first_initialize, "First Initialize 1"); - - first_initialize.Release(); - second_initialize.Release(); - WaitForCompletion(ad_view->Destroy(), "Destroy AdView 2"); - delete ad_view; - } -} - -TEST_F(FirebaseGmaTest, TestAdViewErrorLoadInProgress) { - SKIP_TEST_ON_DESKTOP; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - // Load the AdView ad. - // Note potential flake: this test assumes the attempt to load an ad - // won't resolve immediately. If it does then the result may be two - // successful ad loads instead of the expected - // kAdErrorCodeLoadInProgress error. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future first_load_ad = - ad_view->LoadAd(request); - firebase::Future second_load_ad = - ad_view->LoadAd(request); - - WaitForCompletion(second_load_ad, "Second LoadAd", - firebase::gma::kAdErrorCodeLoadInProgress); - WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); - - const firebase::gma::AdResult* result_ptr = second_load_ad.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeLoadInProgress); - EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); - EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - first_load_ad.Release(); - second_load_ad.Release(); - - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewErrorBadAdUnitId) { - SKIP_TEST_ON_DESKTOP; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBadAdUnit, banner_ad_size), - "Initialize"); - - // Load the AdView ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad = ad_view->LoadAd(request); - WaitForCompletion(load_ad, "LoadAd", - firebase::gma::kAdErrorCodeInvalidRequest); - - const firebase::gma::AdResult* result_ptr = load_ad.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeInvalidRequest); - - EXPECT_FALSE(result_ptr->ad_error().message().empty()); - EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); - - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - load_ad.Release(); - - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; -} - -TEST_F(FirebaseGmaTest, TestAdViewErrorBadExtrasClassName) { - SKIP_TEST_ON_DESKTOP; - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - - // Load the AdView ad. - firebase::gma::AdRequest request = GetAdRequest(); - request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); - WaitForCompletion(ad_view->LoadAd(request), "LoadAd", - firebase::gma::kAdErrorCodeAdNetworkClassLoadError); - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; -} - -// Other InterstitialAd Tests - -TEST_F(FirebaseGmaTest, TestInterstitialAdLoadEmptyRequest) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::InterstitialAd* interstitial = - new firebase::gma::InterstitialAd(); - - WaitForCompletion(interstitial->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the InterstitialAd is initialized, load an ad. - firebase::gma::AdRequest request; - - firebase::Future load_ad_future = - interstitial->LoadAd(kInterstitialAdUnit, request); - - // This test behaves differently if it's running in UI mode - // (manually on a device) or in non-UI mode (via automated tests). - if (ShouldRunUITests()) { - // Run in manual mode: fail if any error occurs. - WaitForCompletion(load_ad_future, "LoadAd"); - } else { - // Run in automated test mode: don't fail if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - } - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - } - load_ad_future.Release(); - delete interstitial; -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdErrorNotInitialized) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - - firebase::gma::AdRequest request = GetAdRequest(); - WaitForCompletion(interstitial_ad->LoadAd(kInterstitialAdUnit, request), - "LoadAd", firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(interstitial_ad->Show(), "Show", - firebase::gma::kAdErrorCodeUninitialized); - - delete interstitial_ad; -} - -TEST_F(FirebaseGmaTest, TesInterstitialAdErrorAlreadyInitialized) { - SKIP_TEST_ON_DESKTOP; - - { - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - firebase::Future first_initialize = - interstitial_ad->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - interstitial_ad->Initialize(app_framework::GetWindowContext()); - - WaitForCompletion(first_initialize, "First Initialize 1"); - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - - first_initialize.Release(); - second_initialize.Release(); - - delete interstitial_ad; - } - - // Reverse the order of the completion waits. - { - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - firebase::Future first_initialize = - interstitial_ad->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - interstitial_ad->Initialize(app_framework::GetWindowContext()); - - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - WaitForCompletion(first_initialize, "First Initialize 1"); - - first_initialize.Release(); - second_initialize.Release(); - - delete interstitial_ad; - } -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdErrorLoadInProgress) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - WaitForCompletion( - interstitial_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the interstitial ad. - // Note potential flake: this test assumes the attempt to load an ad - // won't resolve immediately. If it does then the result may be two - // successful ad loads instead of the expected - // kAdErrorCodeLoadInProgress error. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future first_load_ad = - interstitial_ad->LoadAd(kInterstitialAdUnit, request); - firebase::Future second_load_ad = - interstitial_ad->LoadAd(kInterstitialAdUnit, request); - - WaitForCompletion(second_load_ad, "Second LoadAd", - firebase::gma::kAdErrorCodeLoadInProgress); - WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); - - const firebase::gma::AdResult* result_ptr = second_load_ad.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeLoadInProgress); - EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); - EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - first_load_ad.Release(); - second_load_ad.Release(); - delete interstitial_ad; -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdErrorBadAdUnitId) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - WaitForCompletion( - interstitial_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the interstitial ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - interstitial_ad->LoadAd(kBadAdUnit, request); - WaitForCompletion(load_ad_future, "LoadAd", - firebase::gma::kAdErrorCodeInvalidRequest); - - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeInvalidRequest); - EXPECT_FALSE(result_ptr->ad_error().message().empty()); - EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - load_ad_future.Release(); - delete interstitial_ad; -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdErrorBadExtrasClassName) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::InterstitialAd* interstitial_ad = - new firebase::gma::InterstitialAd(); - WaitForCompletion( - interstitial_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the interstitial ad. - firebase::gma::AdRequest request = GetAdRequest(); - request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); - WaitForCompletion(interstitial_ad->LoadAd(kInterstitialAdUnit, request), - "LoadAd", - firebase::gma::kAdErrorCodeAdNetworkClassLoadError); - delete interstitial_ad; -} - -// Other RewardedAd Tests. - -TEST_F(FirebaseGmaTest, TestRewardedAdLoadEmptyRequest) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - // Note: while showing an ad requires user interaction in another test, - // this test is mean as a baseline loadAd functionality test. - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the RewardedAd is initialized, load an ad. - firebase::gma::AdRequest request; - firebase::Future load_ad_future = - rewarded->LoadAd(kRewardedAdUnit, request); - - // This test behaves differently if it's running in UI mode - // (manually on a device) or in non-UI mode (via automated tests). - if (ShouldRunUITests()) { - // Run in manual mode: fail if any error occurs. - WaitForCompletion(load_ad_future, "LoadAd"); - } else { - // Run in automated test mode: don't fail if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - } - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - // In UI mode, or in non-UI mode if a NoFill error didn't occur, check that - // the ad loaded correctly. - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - } - load_ad_future.Release(); - delete rewarded; -} - -TEST_F(FirebaseGmaTest, TestRewardedAdErrorNotInitialized) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::RewardedAd* rewarded_ad = new firebase::gma::RewardedAd(); - - firebase::gma::AdRequest request = GetAdRequest(); - WaitForCompletion(rewarded_ad->LoadAd(kRewardedAdUnit, request), "LoadAd", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(rewarded_ad->Show(/*listener=*/nullptr), "Show", - firebase::gma::kAdErrorCodeUninitialized); - - delete rewarded_ad; -} - -TEST_F(FirebaseGmaTest, TesRewardedAdErrorAlreadyInitialized) { - SKIP_TEST_ON_DESKTOP; - - { - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - firebase::Future first_initialize = - rewarded->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - rewarded->Initialize(app_framework::GetWindowContext()); - - WaitForCompletionAnyResult(first_initialize, "First Initialize 1"); - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - - first_initialize.Release(); - second_initialize.Release(); - - delete rewarded; - } - - // Reverse the order of the completion waits. - { - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - firebase::Future first_initialize = - rewarded->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - rewarded->Initialize(app_framework::GetWindowContext()); - - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - WaitForCompletionAnyResult(first_initialize, "First Initialize 1"); - - first_initialize.Release(); - second_initialize.Release(); - - delete rewarded; - } -} - -TEST_F(FirebaseGmaTest, TestRewardedAdErrorLoadInProgress) { - SKIP_TEST_ON_DESKTOP; - - // TODO(@drsanta): remove when GMA whitelists CI devices. - TEST_REQUIRES_USER_INTERACTION_ON_IOS; - - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the rewarded ad. - // Note potential flake: this test assumes the attempt to load an ad - // won't resolve immediately. If it does then the result may be two - // successful ad loads instead of the expected - // kAdErrorCodeLoadInProgress error. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future first_load_ad = - rewarded->LoadAd(kRewardedAdUnit, request); - firebase::Future second_load_ad = - rewarded->LoadAd(kRewardedAdUnit, request); - - WaitForCompletion(second_load_ad, "Second LoadAd", - firebase::gma::kAdErrorCodeLoadInProgress); - WaitForCompletionAnyResult(first_load_ad, "First LoadAd"); - - const firebase::gma::AdResult* result_ptr = second_load_ad.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeLoadInProgress); - EXPECT_EQ(result_ptr->ad_error().message(), "Ad is currently loading."); - EXPECT_EQ(result_ptr->ad_error().domain(), "SDK"); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - first_load_ad.Release(); - second_load_ad.Release(); - delete rewarded; -} - -TEST_F(FirebaseGmaTest, TestRewardedAdErrorBadAdUnitId) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the rewarded ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - rewarded->LoadAd(kBadAdUnit, request); - WaitForCompletion(load_ad_future, "LoadAd", - firebase::gma::kAdErrorCodeInvalidRequest); - - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeInvalidRequest); - EXPECT_FALSE(result_ptr->ad_error().message().empty()); - EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - load_ad_future.Release(); - delete rewarded; -} - -TEST_F(FirebaseGmaTest, TestRewardedAdErrorBadExtrasClassName) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the rewarded ad. - firebase::gma::AdRequest request = GetAdRequest(); - request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); - WaitForCompletion(rewarded->LoadAd(kRewardedAdUnit, request), "LoadAd", - firebase::gma::kAdErrorCodeAdNetworkClassLoadError); - delete rewarded; -} - -// Other NativeAd Tests - -TEST_F(FirebaseGmaTest, TestNativeAdLoadEmptyRequest) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // When the NativeAd is initialized, load an ad. - firebase::gma::AdRequest request; - - firebase::Future load_ad_future = - native_ad->LoadAd(kNativeAdUnit, request); - - // Don't fail loadAd, if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - EXPECT_FALSE(result_ptr->response_info().adapter_responses().empty()); - EXPECT_FALSE( - result_ptr->response_info().mediation_adapter_class_name().empty()); - EXPECT_FALSE(result_ptr->response_info().response_id().empty()); - EXPECT_FALSE(result_ptr->response_info().ToString().empty()); - } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { - LogWarning("LoadAd returned NoFill in TestNativeAdLoadEmptyRequest"); - } - - load_ad_future.Release(); - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestNativeRecordImpression) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Set the listener. - TestAdListener ad_listener; - native_ad->SetAdListener(&ad_listener); - - // When the NativeAd is initialized, load an ad. - firebase::Future load_ad_future = - native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); - - // Don't fail loadAd, if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - - // Proceed verifying the RecordImpression, only when loadAd is successful. - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - - firebase::Variant impression_payload = GetVariantMap(); - -#if defined(ANDROID) - // Android doesn't have a return type for this API. - WaitForCompletion(native_ad->RecordImpression(impression_payload), - "RecordImpression"); -#else // iOS - // Test Ad unit IDs are not allowlisted to record impression and the request - // is expected to be rejected by the server. iOS returns the failure. - WaitForCompletion(native_ad->RecordImpression(impression_payload), - "RecordImpression", - firebase::gma::kAdErrorCodeInvalidRequest); -#endif - - // Use an allowlisted Ad unit ID that can record an impression, to verify - // the impression count while testing locally. - EXPECT_EQ(ad_listener.num_on_ad_impression_, 0); - - firebase::Variant str_variant = - firebase::Variant::FromMutableString("test"); - WaitForCompletion(native_ad->RecordImpression(str_variant), - "RecordImpression 2", - firebase::gma::kAdErrorCodeInvalidArgument); - } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { - LogWarning("LoadAd returned NoFill in TestNativeRecordImpression"); - } - - load_ad_future.Release(); - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestNativePerformClick) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_SIMULATOR; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Set the listener. - TestAdListener ad_listener; - native_ad->SetAdListener(&ad_listener); - - // When the NativeAd is initialized, load an ad. - firebase::Future load_ad_future = - native_ad->LoadAd(kNativeAdUnit, GetAdRequest()); - - // Don't fail loadAd, if NoFill occurred. - WaitForCompletion( - load_ad_future, "LoadAd (ignoring NoFill error)", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - - // Proceed verifying the PerformClick, only when loadAd is successful. - if (load_ad_future.error() == firebase::gma::kAdErrorCodeNone) { - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_TRUE(result_ptr->is_successful()); - - firebase::Variant click_payload = GetVariantMap(); - - // Android and iOS doesn't have a return type for this API. - WaitForCompletion(native_ad->PerformClick(click_payload), "PerformClick"); - - // Test Ad unit IDs are not allowlisted to use PerformClick API and the - // request is expected to be rejected by the server. Use an allowlisted Ad - // unit ID to verify the ad click count while testing locally. - EXPECT_EQ(ad_listener.num_on_ad_clicked_, 0); - - firebase::Variant str_variant = - firebase::Variant::FromMutableString("test"); - WaitForCompletion(native_ad->PerformClick(str_variant), "PerformClick 2", - firebase::gma::kAdErrorCodeInvalidArgument); - } else if (load_ad_future.error() == firebase::gma::kAdErrorCodeNoFill) { - LogWarning("LoadAd returned NoFill in TestNativePerformClick"); - } - - load_ad_future.Release(); - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestNativeAdErrorNotInitialized) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - - firebase::gma::AdRequest request = GetAdRequest(); - WaitForCompletion(native_ad->LoadAd(kNativeAdUnit, request), "LoadAd", - firebase::gma::kAdErrorCodeUninitialized); - - firebase::Variant variant = firebase::Variant::EmptyMap(); - WaitForCompletion(native_ad->RecordImpression(variant), "RecordImpression", - firebase::gma::kAdErrorCodeUninitialized); - WaitForCompletion(native_ad->PerformClick(variant), "PerformClick", - firebase::gma::kAdErrorCodeUninitialized); - - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestNativeAdErrorAlreadyInitialized) { - SKIP_TEST_ON_DESKTOP; - - { - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - firebase::Future first_initialize = - native_ad->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - native_ad->Initialize(app_framework::GetWindowContext()); - - WaitForCompletion(first_initialize, "First Initialize 1"); - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - - first_initialize.Release(); - second_initialize.Release(); - - delete native_ad; - } - - // Reverse the order of the completion waits. - { - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - firebase::Future first_initialize = - native_ad->Initialize(app_framework::GetWindowContext()); - firebase::Future second_initialize = - native_ad->Initialize(app_framework::GetWindowContext()); - - WaitForCompletion(second_initialize, "Second Initialize 1", - firebase::gma::kAdErrorCodeAlreadyInitialized); - WaitForCompletion(first_initialize, "First Initialize 1"); - - first_initialize.Release(); - second_initialize.Release(); - - delete native_ad; - } -} - -TEST_F(FirebaseGmaTest, TestNativeAdErrorBadAdUnitId) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the native ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future load_ad_future = - native_ad->LoadAd(kBadAdUnit, request); - WaitForCompletion(load_ad_future, "LoadAd", - firebase::gma::kAdErrorCodeInvalidRequest); - - const firebase::gma::AdResult* result_ptr = load_ad_future.result(); - ASSERT_NE(result_ptr, nullptr); - EXPECT_FALSE(result_ptr->is_successful()); - EXPECT_EQ(result_ptr->ad_error().code(), - firebase::gma::kAdErrorCodeInvalidRequest); - EXPECT_FALSE(result_ptr->ad_error().message().empty()); - EXPECT_EQ(result_ptr->ad_error().domain(), kErrorDomain); - const firebase::gma::ResponseInfo response_info = - result_ptr->ad_error().response_info(); - EXPECT_TRUE(response_info.adapter_responses().empty()); - - load_ad_future.Release(); - delete native_ad; -} - -TEST_F(FirebaseGmaTest, TestNativeAdErrorBadExtrasClassName) { - SKIP_TEST_ON_DESKTOP; - - firebase::gma::NativeAd* native_ad = new firebase::gma::NativeAd(); - WaitForCompletion(native_ad->Initialize(app_framework::GetWindowContext()), - "Initialize"); - - // Load the native ad. - firebase::gma::AdRequest request = GetAdRequest(); - request.add_extra(kAdNetworkExtrasInvalidClassName, "shouldnot", "work"); - WaitForCompletion(native_ad->LoadAd(kNativeAdUnit, request), "LoadAd", - firebase::gma::kAdErrorCodeAdNetworkClassLoadError); - delete native_ad; -} - -// Stress tests. These take a while so run them near the end. -TEST_F(FirebaseGmaTest, TestAdViewStress) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_EMULATOR; - - // TODO(@drsanta): remove when GMA whitelists CI devices - TEST_REQUIRES_USER_INTERACTION_ON_IOS; - TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; - - for (int i = 0; i < 10; ++i) { - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "TestAdViewStress Initialize"); - - // Load the AdView ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future future = ad_view->LoadAd(request); - WaitForCompletion( - future, "TestAdViewStress LoadAd", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - // Stress tests may exhaust the ad pool. If so, loadAd will return - // kAdErrorCodeNoFill. - if (future.error() == firebase::gma::kAdErrorCodeNone) { - EXPECT_EQ(ad_view->ad_size().width(), kBannerWidth); - EXPECT_EQ(ad_view->ad_size().height(), kBannerHeight); - } - WaitForCompletion(ad_view->Destroy(), "Destroy the AdView"); - delete ad_view; - } -} - -TEST_F(FirebaseGmaTest, TestInterstitialAdStress) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_EMULATOR; - - // TODO(@drsanta): remove when GMA whitelists CI devices - TEST_REQUIRES_USER_INTERACTION_ON_IOS; - TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; - - for (int i = 0; i < 10; ++i) { - firebase::gma::InterstitialAd* interstitial = - new firebase::gma::InterstitialAd(); - - WaitForCompletion( - interstitial->Initialize(app_framework::GetWindowContext()), - "TestInterstitialAdStress Initialize"); - - // When the InterstitialAd is initialized, load an ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future future = - interstitial->LoadAd(kInterstitialAdUnit, request); - WaitForCompletion( - future, "TestInterstitialAdStress LoadAd", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - // Stress tests may exhaust the ad pool. If so, loadAd will return - // kAdErrorCodeNoFill. - delete interstitial; - } -} - -TEST_F(FirebaseGmaTest, TestRewardedAdStress) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_EMULATOR; - - // TODO(@drsanta): remove when GMA whitelists CI devices. - TEST_REQUIRES_USER_INTERACTION_ON_IOS; - TEST_REQUIRES_USER_INTERACTION_ON_ANDROID; - - for (int i = 0; i < 10; ++i) { - firebase::gma::RewardedAd* rewarded = new firebase::gma::RewardedAd(); - - WaitForCompletion(rewarded->Initialize(app_framework::GetWindowContext()), - "TestRewardedAdStress Initialize"); - - // When the RewardedAd is initialized, load an ad. - firebase::gma::AdRequest request = GetAdRequest(); - firebase::Future future = - rewarded->LoadAd(kRewardedAdUnit, request); - WaitForCompletion( - future, "TestRewardedAdStress LoadAd", - {firebase::gma::kAdErrorCodeNone, firebase::gma::kAdErrorCodeNoFill}); - // Stress tests may exhaust the ad pool. If so, loadAd will return - // kAdErrorCodeNoFill. - delete rewarded; - } -} - -#if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) -// Test runs & compiles for phones only. - -struct ThreadArgs { - firebase::gma::AdView* ad_view; - sem_t* semaphore; -}; - -static void* DeleteAdViewOnSignal(void* args) { - ThreadArgs* thread_args = static_cast(args); - sem_wait(thread_args->semaphore); - delete thread_args->ad_view; - return nullptr; -} - -TEST_F(FirebaseGmaTest, TestAdViewMultithreadDeletion) { - SKIP_TEST_ON_DESKTOP; - SKIP_TEST_ON_MOBILE; // TODO(b/172832275): This test is temporarily - // disabled on all platforms due to flakiness - // on Android. Once it's fixed, this test should - // be re-enabled on mobile. - - const firebase::gma::AdSize banner_ad_size(kBannerWidth, kBannerHeight); - - for (int i = 0; i < 5; ++i) { - firebase::gma::AdView* ad_view = new firebase::gma::AdView(); - WaitForCompletion(ad_view->Initialize(app_framework::GetWindowContext(), - kBannerAdUnit, banner_ad_size), - "Initialize"); - sem_t semaphore; - sem_init(&semaphore, 0, 1); - - ThreadArgs args = {ad_view, &semaphore}; - - pthread_t t1; - int err = pthread_create(&t1, nullptr, &DeleteAdViewOnSignal, &args); - EXPECT_EQ(err, 0); - - ad_view->Destroy(); - sem_post(&semaphore); - - // Blocks until DeleteAdViewOnSignal function is done. - void* result = nullptr; - err = pthread_join(t1, &result); - - EXPECT_EQ(err, 0); - EXPECT_EQ(result, nullptr); - - sem_destroy(&semaphore); - } -} -#endif // #if defined(ANDROID) || (defined(TARGET_OS_IPHONE) && - // TARGET_OS_IPHONE) - -class FirebaseGmaUmpTest : public FirebaseGmaTest { - public: - FirebaseGmaUmpTest() : consent_info_(nullptr) {} - - // Whether to call ConsentInfo::Reset() upon initialization, which - // resets UMP's consent state to as if the app was first installed. - enum ResetOption { kReset, kNoReset }; - - void InitializeUmp(ResetOption reset = kReset); - void TerminateUmp(ResetOption reset = kReset); - - void SetUp() override; - void TearDown() override; - - protected: - firebase::gma::ump::ConsentInfo* consent_info_; -}; - -void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) { - using firebase::gma::ump::ConsentInfo; +void FirebaseUmpTest::InitializeUmp(ResetOption reset) { + using firebase::ump::ConsentInfo; firebase::InitResult result; consent_info_ = ConsentInfo::GetInstance(*shared_app_, &result); @@ -2611,7 +120,7 @@ void FirebaseGmaUmpTest::InitializeUmp(ResetOption reset) { } } -void FirebaseGmaUmpTest::TerminateUmp(ResetOption reset) { +void FirebaseUmpTest::TerminateUmp(ResetOption reset) { if (consent_info_) { if (reset == kReset) { consent_info_->Reset(); @@ -2621,38 +130,36 @@ void FirebaseGmaUmpTest::TerminateUmp(ResetOption reset) { } } -void FirebaseGmaUmpTest::SetUp() { - FirebaseGmaTest::SetUp(); +void FirebaseUmpTest::SetUp() { InitializeUmp(); ASSERT_NE(consent_info_, nullptr); } -void FirebaseGmaUmpTest::TearDown() { +void FirebaseUmpTest::TearDown() { TerminateUmp(); - FirebaseGmaTest::TearDown(); } // Tests for User Messaging Platform -TEST_F(FirebaseGmaUmpTest, TestUmpInitialization) { +TEST_F(FirebaseUmpTest, TestUmpInitialization) { // Initialize handled automatically in test setup. EXPECT_NE(consent_info_, nullptr); // Terminate handled automatically in test teardown. } // Tests for User Messaging Platform -TEST_F(FirebaseGmaUmpTest, TestUmpDefaultsToUnknownStatus) { +TEST_F(FirebaseUmpTest, TestUmpDefaultsToUnknownStatus) { EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusUnknown); + firebase::ump::kConsentStatusUnknown); EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusUnknown); + firebase::ump::kConsentFormStatusUnknown); EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), - firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); + firebase::ump::kPrivacyOptionsRequirementStatusUnknown); EXPECT_FALSE(consent_info_->CanRequestAds()); } // Tests for User Messaging Platform -TEST_F(FirebaseGmaUmpTest, TestUmpGetInstanceIsAlwaysEqual) { - using firebase::gma::ump::ConsentInfo; +TEST_F(FirebaseUmpTest, TestUmpGetInstanceIsAlwaysEqual) { + using firebase::ump::ConsentInfo; EXPECT_NE(consent_info_, nullptr); @@ -2678,9 +185,9 @@ TEST_F(FirebaseGmaUmpTest, TestUmpGetInstanceIsAlwaysEqual) { delete second_app; } -TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpRequestConsentInfoUpdate) { + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); @@ -2693,32 +200,32 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdate) { EXPECT_TRUE(future == consent_info_->RequestConsentInfoUpdateLastResult()); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); EXPECT_NE(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusUnknown); + firebase::ump::kConsentStatusUnknown); EXPECT_NE(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusUnknown); + firebase::ump::kConsentFormStatusUnknown); EXPECT_NE(consent_info_->GetPrivacyOptionsRequirementStatus(), - firebase::gma::ump::kPrivacyOptionsRequirementStatusUnknown); + firebase::ump::kPrivacyOptionsRequirementStatusUnknown); } -TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2726,28 +233,28 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugEEA) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); } -TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2755,28 +262,28 @@ TEST_F(FirebaseGmaUmpTest, TestUmpRequestConsentInfoUpdateDebugNonEEA) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); EXPECT_THAT(consent_info_->GetConsentStatus(), - AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), - Eq(firebase::gma::ump::kConsentStatusRequired))); + AnyOf(Eq(firebase::ump::kConsentStatusNotRequired), + Eq(firebase::ump::kConsentStatusRequired))); } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpLoadForm) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2784,40 +291,40 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadForm) { "RequestConsentInfoUpdate"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusAvailable); + firebase::ump::kConsentFormStatusAvailable); // Load the form. Run this step with retry in case of network timeout. WaitForCompletion( RunWithRetry([&]() { return consent_info_->LoadConsentForm(); }), "LoadConsentForm", - {firebase::gma::ump::kConsentFormSuccess, - firebase::gma::ump::kConsentFormErrorTimeout}); + {firebase::ump::kConsentFormSuccess, + firebase::ump::kConsentFormErrorTimeout}); firebase::Future future = consent_info_->LoadConsentFormLastResult(); EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusAvailable); + firebase::ump::kConsentFormStatusAvailable); - if (future.error() == firebase::gma::ump::kConsentFormErrorTimeout) { + if (future.error() == firebase::ump::kConsentFormErrorTimeout) { LogWarning("Timed out after multiple tries, but passing anyway."); } } -TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { +TEST_F(FirebaseUmpTest, TestUmpShowForm) { TEST_REQUIRES_USER_INTERACTION; - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2825,15 +332,15 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { "RequestConsentInfoUpdate"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusAvailable); + firebase::ump::kConsentFormStatusAvailable); WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm"); EXPECT_EQ(consent_info_->GetConsentFormStatus(), - firebase::gma::ump::kConsentFormStatusAvailable); + firebase::ump::kConsentFormStatusAvailable); firebase::Future future = consent_info_->ShowConsentForm(app_framework::GetWindowController()); @@ -2843,23 +350,23 @@ TEST_F(FirebaseGmaUmpTest, TestUmpShowForm) { WaitForCompletion(future, "ShowConsentForm"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusObtained); + firebase::ump::kConsentStatusObtained); } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnderAgeOfConsent) { +TEST_F(FirebaseUmpTest, TestUmpLoadFormUnderAgeOfConsent) { SKIP_TEST_ON_IOS_SIMULATOR; - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); ConsentRequestParameters params; params.tag_for_under_age_of_consent = true; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2867,32 +374,32 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnderAgeOfConsent) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); firebase::Future load_future = consent_info_->LoadConsentForm(); WaitForCompletion(load_future, "LoadConsentForm", - {firebase::gma::ump::kConsentFormErrorUnavailable, - firebase::gma::ump::kConsentFormErrorTimeout, - firebase::gma::ump::kConsentFormSuccess}); + {firebase::ump::kConsentFormErrorUnavailable, + firebase::ump::kConsentFormErrorTimeout, + firebase::ump::kConsentFormSuccess}); } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2900,31 +407,31 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadFormUnavailableDebugNonEEA) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); if (consent_info_->GetConsentStatus() != - firebase::gma::ump::kConsentStatusRequired) { + firebase::ump::kConsentStatusRequired) { WaitForCompletion(consent_info_->LoadConsentForm(), "LoadConsentForm", - firebase::gma::ump::kConsentFormErrorUnavailable); + firebase::ump::kConsentFormErrorUnavailable); } } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; FLAKY_TEST_SECTION_BEGIN(); ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2932,19 +439,19 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion(future, "RequestConsentInfoUpdate", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); // Retry only network errors. - EXPECT_NE(future.error(), firebase::gma::ump::kConsentRequestErrorNetwork); + EXPECT_NE(future.error(), firebase::ump::kConsentRequestErrorNetwork); FLAKY_TEST_SECTION_END(); EXPECT_THAT(consent_info_->GetConsentStatus(), - AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), - Eq(firebase::gma::ump::kConsentStatusRequired))); + AnyOf(Eq(firebase::ump::kConsentStatusNotRequired), + Eq(firebase::ump::kConsentStatusRequired))); if (consent_info_->GetConsentStatus() == - firebase::gma::ump::kConsentStatusNotRequired || + firebase::ump::kConsentStatusNotRequired || ShouldRunUITests()) { // If ConsentStatus is Required, we only want to do this next part if UI // interaction is allowed, as it will show a consent form which won't work @@ -2960,17 +467,17 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugNonEEA) { } } -TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; TEST_REQUIRES_USER_INTERACTION; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -2978,7 +485,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { "RequestConsentInfoUpdate"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); firebase::Future future = consent_info_->LoadAndShowConsentFormIfRequired( @@ -2990,21 +497,21 @@ TEST_F(FirebaseGmaUmpTest, TestUmpLoadAndShowIfRequiredDebugEEA) { WaitForCompletion(future, "LoadAndShowConsentFormIfRequired"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusObtained); + firebase::ump::kConsentStatusObtained); } -TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; - using firebase::gma::ump::PrivacyOptionsRequirementStatus; +TEST_F(FirebaseUmpTest, TestUmpPrivacyOptions) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; + using firebase::ump::PrivacyOptionsRequirementStatus; TEST_REQUIRES_USER_INTERACTION; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3012,7 +519,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { "RequestConsentInfoUpdate"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); EXPECT_FALSE(consent_info_->CanRequestAds()); @@ -3021,7 +528,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { "LoadAndShowConsentFormIfRequired"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusObtained); + firebase::ump::kConsentStatusObtained); EXPECT_TRUE(consent_info_->CanRequestAds()) << "After consent obtained"; @@ -3032,7 +539,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { ProcessEvents(5000); EXPECT_EQ(consent_info_->GetPrivacyOptionsRequirementStatus(), - firebase::gma::ump::kPrivacyOptionsRequirementStatusRequired); + firebase::ump::kPrivacyOptionsRequirementStatusRequired); firebase::Future future = consent_info_->ShowPrivacyOptionsForm( app_framework::GetWindowController()); @@ -3042,15 +549,15 @@ TEST_F(FirebaseGmaUmpTest, TestUmpPrivacyOptions) { WaitForCompletion(future, "ShowPrivacyOptionsForm"); } -TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestCanRequestAdsNonEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3058,24 +565,24 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsNonEEA) { "RequestConsentInfoUpdate"); EXPECT_THAT(consent_info_->GetConsentStatus(), - AnyOf(Eq(firebase::gma::ump::kConsentStatusNotRequired), - Eq(firebase::gma::ump::kConsentStatusRequired))); + AnyOf(Eq(firebase::ump::kConsentStatusNotRequired), + Eq(firebase::ump::kConsentStatusRequired))); if (consent_info_->GetConsentStatus() == - firebase::gma::ump::kConsentStatusNotRequired) { + firebase::ump::kConsentStatusNotRequired) { EXPECT_TRUE(consent_info_->CanRequestAds()); } } -TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { - using firebase::gma::ump::ConsentDebugSettings; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; +TEST_F(FirebaseUmpTest, TestCanRequestAdsEEA) { + using firebase::ump::ConsentDebugSettings; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3083,22 +590,22 @@ TEST_F(FirebaseGmaUmpTest, TestCanRequestAdsEEA) { "RequestConsentInfoUpdate"); EXPECT_EQ(consent_info_->GetConsentStatus(), - firebase::gma::ump::kConsentStatusRequired); + firebase::ump::kConsentStatusRequired); EXPECT_FALSE(consent_info_->CanRequestAds()); } -TEST_F(FirebaseGmaUmpTest, TestUmpCleanupWithDelay) { +TEST_F(FirebaseUmpTest, TestUmpCleanupWithDelay) { // Ensure that if ConsentInfo is deleted after a delay, Futures are // properly invalidated. - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3124,18 +631,18 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCleanupWithDelay) { EXPECT_EQ(future_privacy.status(), firebase::kFutureStatusInvalid); } -TEST_F(FirebaseGmaUmpTest, TestUmpCleanupRaceCondition) { +TEST_F(FirebaseUmpTest, TestUmpCleanupRaceCondition) { // Ensure that if ConsentInfo is deleted immediately, operations // (and their Futures) are properly invalidated. - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3161,17 +668,17 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCleanupRaceCondition) { ProcessEvents(5000); } -TEST_F(FirebaseGmaUmpTest, TestUmpCallbacksOnWrongInstance) { +TEST_F(FirebaseUmpTest, TestUmpCallbacksOnWrongInstance) { // Ensure that if ConsentInfo is deleted and then recreated, stale // callbacks don't call into the new instance and cause crashes. - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3217,14 +724,14 @@ TEST_F(FirebaseGmaUmpTest, TestUmpCallbacksOnWrongInstance) { LogDebug("Done"); } -TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgress) { +TEST_F(FirebaseUmpTest, TestUmpMethodsReturnOperationInProgress) { SKIP_TEST_ON_DESKTOP; SKIP_TEST_ON_IOS_SIMULATOR; // LoadAndShowConsentFormIfRequired // is too quick on simulator. - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; // Check that all of the UMP operations properly return an OperationInProgress // error if called more than once at the same time. @@ -3235,7 +742,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgress) { ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyNonEEA; + firebase::ump::kConsentDebugGeographyNonEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3245,23 +752,23 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgress) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion( future_request_2, "RequestConsentInfoUpdate second", - firebase::gma::ump::kConsentRequestErrorOperationInProgress); + firebase::ump::kConsentRequestErrorOperationInProgress); WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first", - {firebase::gma::ump::kConsentRequestSuccess, - firebase::gma::ump::kConsentRequestErrorNetwork}); + {firebase::ump::kConsentRequestSuccess, + firebase::ump::kConsentRequestErrorNetwork}); consent_info_->Reset(); FLAKY_TEST_SECTION_END(); } -TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { +TEST_F(FirebaseUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { SKIP_TEST_ON_DESKTOP; TEST_REQUIRES_USER_INTERACTION; - using firebase::gma::ump::ConsentFormStatus; - using firebase::gma::ump::ConsentRequestParameters; - using firebase::gma::ump::ConsentStatus; + using firebase::ump::ConsentFormStatus; + using firebase::ump::ConsentRequestParameters; + using firebase::ump::ConsentStatus; // Check that all of the UMP operations properly return an OperationInProgress // error if called more than once at the same time. This test include methods @@ -3270,7 +777,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { ConsentRequestParameters params; params.tag_for_under_age_of_consent = false; params.debug_settings.debug_geography = - firebase::gma::ump::kConsentDebugGeographyEEA; + firebase::ump::kConsentDebugGeographyEEA; params.debug_settings.debug_device_ids = kTestDeviceIDs; params.debug_settings.debug_device_ids.push_back(GetDebugDeviceId()); @@ -3280,13 +787,13 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { consent_info_->RequestConsentInfoUpdate(params); WaitForCompletion( future_request_2, "RequestConsentInfoUpdate second", - firebase::gma::ump::kConsentRequestErrorOperationInProgress); + firebase::ump::kConsentRequestErrorOperationInProgress); WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first"); firebase::Future future_load_1 = consent_info_->LoadConsentForm(); firebase::Future future_load_2 = consent_info_->LoadConsentForm(); WaitForCompletion(future_load_2, "LoadConsentForm second", - firebase::gma::ump::kConsentFormErrorOperationInProgress); + firebase::ump::kConsentFormErrorOperationInProgress); WaitForCompletion(future_load_1, "LoadConsentForm first"); firebase::Future future_show_1 = @@ -3294,7 +801,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { firebase::Future future_show_2 = consent_info_->ShowConsentForm(app_framework::GetWindowController()); WaitForCompletion(future_show_2, "ShowConsentForm second", - firebase::gma::ump::kConsentFormErrorOperationInProgress); + firebase::ump::kConsentFormErrorOperationInProgress); WaitForCompletion(future_show_1, "ShowConsentForm first"); firebase::Future future_privacy_1 = @@ -3304,7 +811,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { consent_info_->ShowPrivacyOptionsForm( app_framework::GetWindowController()); WaitForCompletion(future_privacy_2, "ShowPrivacyOptionsForm second", - firebase::gma::ump::kConsentFormErrorOperationInProgress); + firebase::ump::kConsentFormErrorOperationInProgress); WaitForCompletion(future_privacy_1, "ShowPrivacyOptionsForm first"); consent_info_->Reset(); @@ -3320,7 +827,7 @@ TEST_F(FirebaseGmaUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { app_framework::GetWindowController()); WaitForCompletion(future_load_and_show_2, "LoadAndShowConsentFormIfRequired second", - firebase::gma::ump::kConsentFormErrorOperationInProgress); + firebase::ump::kConsentFormErrorOperationInProgress); WaitForCompletion(future_load_and_show_1, "LoadAndShowConsentFormIfRequired first"); } From bcc01f6e02b57a740abc48ba5587c1c79326f8aa Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:44:40 -0700 Subject: [PATCH 05/27] Update build script. --- scripts/gha/build_ios_tvos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/gha/build_ios_tvos.py b/scripts/gha/build_ios_tvos.py index d314024e58..77e7cd5796 100644 --- a/scripts/gha/build_ios_tvos.py +++ b/scripts/gha/build_ios_tvos.py @@ -53,7 +53,8 @@ 'firebase_dynamic_links', 'firebase_firestore', 'firebase_functions', 'firebase_gma', 'firebase_installations', 'firebase_messaging', - 'firebase_remote_config', 'firebase_storage'), + 'firebase_remote_config', 'firebase_storage', + 'firebase_ump'), 'device': { 'architectures' : ['arm64'], 'toolchain' : 'cmake/toolchains/ios.cmake', @@ -592,7 +593,7 @@ def parse_cmdline_args(): 'firebase_dynamic_links', 'firebase_firestore', 'firebase_functions', 'firebase_gma', 'firebase_installations', 'firebase_messaging', - 'firebase_remote_config', 'firebase_storage'), + 'firebase_remote_config', 'firebase_storage', 'firebase_ump'), help='List of CMake build targets') parser.add_argument('-o', '--os', nargs='+', default=('ios', 'tvos'), help='List of operating systems to build for.') From 48cbbe3545ec310f1a777fab0ab85b7528ab011b Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:46:05 -0700 Subject: [PATCH 06/27] Remove a few other mentions of GMA here. --- ump/integration_test/res/values/strings.xml | 2 +- ump/src/include/firebase/ump/types.h | 2 -- ump/ump_resources/build.gradle | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ump/integration_test/res/values/strings.xml b/ump/integration_test/res/values/strings.xml index ff36817020..cc3d8f9217 100644 --- a/ump/integration_test/res/values/strings.xml +++ b/ump/integration_test/res/values/strings.xml @@ -16,5 +16,5 @@ */ --> - Firebase GMA Integration Test + Firebase UMP Integration Test diff --git a/ump/src/include/firebase/ump/types.h b/ump/src/include/firebase/ump/types.h index 09329326d2..d376bff85d 100644 --- a/ump/src/include/firebase/ump/types.h +++ b/ump/src/include/firebase/ump/types.h @@ -33,7 +33,6 @@ extern "C" { // FIREBASE_PLATFORM_TVOS namespace firebase { -namespace gma { namespace ump { /// Debug values for testing geography. @@ -173,7 +172,6 @@ enum PrivacyOptionsRequirementStatus { }; } // namespace ump -} // namespace gma } // namespace firebase #endif // FIREBASE_UMP_SRC_INCLUDE_FIREBASE_UMP_TYPES_H_ diff --git a/ump/ump_resources/build.gradle b/ump/ump_resources/build.gradle index 62140c669a..042ccd2266 100644 --- a/ump/ump_resources/build.gradle +++ b/ump/ump_resources/build.gradle @@ -49,7 +49,7 @@ android { main { manifest.srcFile 'AndroidManifest.xml' java { - srcDirs = ['../src_java/com/google/firebase/gma/internal/cpp'] + srcDirs = ['../src_java/com/google/firebase/ump/internal/cpp'] } } } @@ -58,7 +58,6 @@ android { dependencies { implementation platform('com.google.firebase:firebase-bom:33.11.0') implementation 'com.google.firebase:firebase-analytics' - implementation 'com.google.android.gms:play-services-ads:23.0.0' implementation 'com.google.android.ump:user-messaging-platform:2.2.0' } @@ -67,4 +66,4 @@ afterEvaluate { } apply from: "$rootDir/android_build_files/extract_and_dex.gradle" -extractAndDexAarFile('gma_resources') +extractAndDexAarFile('ump_resources') From 39996db64c125e1ad91809bf35571f4e45d2f961 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:54:37 -0700 Subject: [PATCH 07/27] Update readme. --- release_build_files/readme.md | 61 ++++++++++++++++++++++++++--------- settings.gradle | 4 ++- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index ae6d754b2c..c3596c53b6 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -10,12 +10,13 @@ on *iOS* and *Android*: * Firebase Dynamic Links (deprecated SDK) * Cloud Firestore * Firebase Functions -* Google Mobile Ads (with User Messaging Platform) +* Google Mobile Ads * Firebase Installations * Firebase Instance ID (deprecated SDK) * Firebase Realtime Database * Firebase Remote Config * Firebase Storage +* User Messaging Platform ## Desktop Workflow Implementations @@ -174,6 +175,12 @@ Firebase Storage | libfirebase_storage.a | | (Maven package) | | com.google.firebase:firebase-auth | | (Maven package) +User Messaging Platform | libfirebase_ump.a +| | libfirebase_app.a +| | com.google.firebase:firebase-analytics +| | (Maven package) +| | com.google.android.ump:user-messaging-platform:2.2.0 +| | (Maven package) Google Play services module| com.google.android.gms:play-services-base:18.6.0 | | (Maven package) @@ -206,6 +213,7 @@ firebaseCpp.dependencies { messaging remoteConfig storage + ump } ``` @@ -274,6 +282,10 @@ Firebase Storage | firebase_storage.xcframework | | firebase.xcframework | | Firebase/Storage Cocoapod (11.10.0) | | Firebase/Auth Cocoapod (11.10.0) +User Messaging Platform | firebase_ump.xcframework +| | firebase.xcframework +| | Firebase/CoreOnly Cocoapod (11.10.0) +| | GoogleUserMessagingPlatform Cocoapod (2.3.0) Important: Each version of the Firebase C++ SDK supports a specific version of the Firebase iOS SDK. Please ensure that you reference the Cocoapod versions @@ -337,6 +349,10 @@ Firebase Storage | libfirebase_storage.a | | libfirebase_auth.a | | Firebase/Storage Cocoapod (11.10.0) | | Firebase/Auth Cocoapod (11.10.0) +User Messaging Platform | libfirebase_ump.a +| | libfirebase_app.a +| | Firebase/CoreOnly Cocoapod (11.10.0) +| | GoogleUserMessagingPlatform Cocoapod (2.3.0) Important: Each version of the Firebase C++ SDK supports a specific version of the Firebase iOS SDK. Please ensure that you reference the Cocoapod versions @@ -387,6 +403,8 @@ Firebase Installations (stub) | libfirebase_installations.a | | libfirebase_app.a Firebase Cloud Messaging (stub) | libfirebase_messaging.a | | libfirebase_app.a +User Messaging Platform (stub) | libfirebase_ump.a +| | libfirebase_app.a The provided libraries have been tested using GCC 4.8.0, GCC 7.2.0, and Clang 5.0 on Ubuntu. When building C++ desktop apps on Linux, you will need to link @@ -430,6 +448,8 @@ Firebase Installations (stub) | firebase_installations.framework | | firebase.framework Firebase Cloud Messaging (stub) | firebase_messaging.framework | | firebase.framework +User Messaging Platform (stub) | libfirebase_ump.a +| | libfirebase_app.a The provided libraries have been tested using Xcode 14.1. When building C++ desktop apps on OS X, you will need to link the `gssapi_krb5` and `pthread` @@ -474,6 +494,8 @@ Firebase Installations (stub) | firebase_installations.lib | | firebase_app.lib Firebase Cloud Messaging (stub) | firebase_messaging.lib | | firebase_app.lib +User Messaging Platform (stub) | firebase_ump.lib +| | firebase_app.lib The provided libraries have been tested using Visual Studio 2019. When building C++ desktop apps on Windows, you will need to link the following @@ -577,21 +599,22 @@ initialization status. These can be used without Google Play services. The table below summarizes whether Google Play services is required by each Firebase C++ library. -Firebase C++ Library | Google Play services required? --------------------- | --------------------------------- -Analytics | Not required -App Check | Not required -Cloud Messaging | Required -Auth | Required -Dynamic Links | Required -Firestore | Required -Functions | Required -Installations | Not Required -Instance ID | Required -Google Mobile Ads | Not required (usually; see below) -Realtime Database | Required -Remote Config | Required -Storage | Required +Firebase C++ Library | Google Play services required? +------------------------ | --------------------------------- +Analytics | Not required +App Check | Not required +Cloud Messaging | Required +Auth | Required +Dynamic Links | Required +Firestore | Required +Functions | Required +Installations | Not Required +Instance ID | Required +Google Mobile Ads | Not required (usually; see below) +Realtime Database | Required +Remote Config | Required +Storage | Required +User Messaging Platform | Not required #### A note on Google Mobile Ads and Google Play services @@ -631,6 +654,12 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### 12.8.0 +- Changes + - UMP: Moved the User Messaging Platform SDK to its own top-level + library and to the firebase::ump namespace. The version in the + GMA library (in firebase::gma::ump) will be deprecated soon. + ### 12.7.0 - Changes - General (iOS): Update to Firebase Cocoapods version 11.10.0. diff --git a/settings.gradle b/settings.gradle index 0ec81050fc..578462cd41 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,4 +22,6 @@ include ':app', ':remote_config', ':remote_config:remote_config_resources', ':storage', - ':storage:storage_resources' \ No newline at end of file + ':storage:storage_resources' + ':ump', + ':ump:gma_resources', From 96999c9a07f3c4f3cd9c1a5e74c116065e67965f Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:55:48 -0700 Subject: [PATCH 08/27] Update testapp configurator. --- scripts/gha/integration_testing/build_testapps.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/gha/integration_testing/build_testapps.json b/scripts/gha/integration_testing/build_testapps.json index aa57936b3d..4e0c479144 100755 --- a/scripts/gha/integration_testing/build_testapps.json +++ b/scripts/gha/integration_testing/build_testapps.json @@ -112,6 +112,19 @@ ], "provision": "Google_Development.mobileprovision" }, + { + "name": "ump", + "full_name": "FirebaseUmp", + "bundle_id": "com.google.ios.admob.testapp", + "ios_target": "integration_test", + "tvos_target": "", + "testapp_path": "ump/integration_test", + "frameworks": [ + "firebase_ump.xcframework", + "firebase.xcframework" + ], + "provision": "Google_Development.mobileprovision" + }, { "name": "installations", "full_name": "FirebaseInstallations", From ffc3d0e309e41ae4664459fac016e9188bd8ad03 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 11:57:42 -0700 Subject: [PATCH 09/27] Update Android dependencies. --- Android/firebase_dependencies.gradle | 9 ++++++--- build_scripts/ios/build.sh | 2 +- release_build_files/Android/firebase_dependencies.gradle | 8 +++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Android/firebase_dependencies.gradle b/Android/firebase_dependencies.gradle index 0394146816..cfba6e93a1 100644 --- a/Android/firebase_dependencies.gradle +++ b/Android/firebase_dependencies.gradle @@ -29,8 +29,6 @@ def firebaseDependenciesMap = [ 'functions' : ['com.google.firebase:firebase-functions'], 'gma' : ['com.google.android.gms:play-services-ads:23.0.0', 'com.google.android.ump:user-messaging-platform:2.2.0'], - 'ump' : ['com.google.android.gms:play-services-ads:23.0.0', - 'com.google.android.ump:user-messaging-platform:2.2.0'], 'installations' : ['com.google.firebase:firebase-installations'], 'invites' : ['com.google.firebase:firebase-invites'], // Messaging has an additional local dependency to include. @@ -41,7 +39,8 @@ def firebaseDependenciesMap = [ 'performance' : ['com.google.firebase:firebase-perf'], 'remote_config' : ['com.google.firebase:firebase-config'], 'storage' : ['com.google.firebase:firebase-storage'], - 'testlab' : [] + 'testlab' : [], + 'ump' : ['com.google.android.ump:user-messaging-platform:2.2.0'] ] // A map of library to the gradle resources that they depend upon. @@ -56,6 +55,7 @@ def firebaseResourceDependenciesMap = [ 'gma' : [':gma:gma_resources'], 'remote_config' : [':remote_config:remote_config_resources'], 'storage' : [':storage:storage_resources'] + 'ump' : [':ump:ump_resources'], ] def setResourceDependencies(String subproject) { @@ -118,6 +118,9 @@ class Dependencies { def getStorage() { libSet.add('storage') } + def getUmp() { + libSet.add('ump') + } } // Extension to handle which Firebase C++ dependencies are being added to the diff --git a/build_scripts/ios/build.sh b/build_scripts/ios/build.sh index 12ac66e93b..c466d35af7 100755 --- a/build_scripts/ios/build.sh +++ b/build_scripts/ios/build.sh @@ -27,7 +27,7 @@ readonly SUPPORTED_PLATFORMS=(device simulator) readonly SUPPORTED_ARCHITECTURES=(arm64 x86_64) readonly DEVICE_ARCHITECTURES=(arm64) readonly SIMULATOR_ARCHITECTURES=(arm64 x86_64) -readonly SUPPORTED_TARGETS=(firebase_analytics firebase_auth firebase_app_check firebase_database firebase_dynamic_links firebase_firestore firebase_functions firebase_gma firebase_installations firebase_messaging firebase_remote_config firebase_storage) +readonly SUPPORTED_TARGETS=(firebase_analytics firebase_auth firebase_app_check firebase_database firebase_dynamic_links firebase_firestore firebase_functions firebase_gma firebase_installations firebase_messaging firebase_remote_config firebase_storage firebase_ump) # build default value buildpath="ios_build" diff --git a/release_build_files/Android/firebase_dependencies.gradle b/release_build_files/Android/firebase_dependencies.gradle index a024285ea9..6a6ab2740b 100644 --- a/release_build_files/Android/firebase_dependencies.gradle +++ b/release_build_files/Android/firebase_dependencies.gradle @@ -29,8 +29,6 @@ def firebaseDependenciesMap = [ 'functions' : ['com.google.firebase:firebase-functions'], 'gma' : ['com.google.android.gms:play-services-ads:23.0.0', 'com.google.android.ump:user-messaging-platform:2.2.0'], - 'ump' : ['com.google.android.gms:play-services-ads:23.0.0', - 'com.google.android.ump:user-messaging-platform:2.2.0'], 'installations' : ['com.google.firebase:firebase-installations'], 'invites' : ['com.google.firebase:firebase-invites'], // Messaging has an additional local dependency to include. @@ -41,7 +39,8 @@ def firebaseDependenciesMap = [ 'performance' : ['com.google.firebase:firebase-perf'], 'remote_config' : ['com.google.firebase:firebase-config'], 'storage' : ['com.google.firebase:firebase-storage'], - 'testlab' : [] + 'testlab' : [], + 'ump' : ['com.google.android.ump:user-messaging-platform:2.2.0'] ] // Handles adding the Firebase C++ dependencies as properties. @@ -100,6 +99,9 @@ class Dependencies { def getTestlab() { libSet.add('testlab') } + def getUmp() { + libSet.add('ump') + } } // Extension to handle which Firebase C++ dependencies are being added to the From d1447006a2a928140c0bca838b3c383313ee9b15 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 12:01:38 -0700 Subject: [PATCH 10/27] Some few lingering changes needed. --- CMakeLists.txt | 2 +- app/CMakeLists.txt | 8 +++++++- settings.gradle | 2 +- setup_integration_tests.py | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee714465cd..9b683ebc95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -634,7 +634,7 @@ if (FIREBASE_INCLUDE_GMA) add_subdirectory(gma) endif() if (FIREBASE_INCLUDE_UMP) - add_subdirectory(gma) + add_subdirectory(ump) endif() if (FIREBASE_INCLUDE_INSTALLATIONS) add_subdirectory(installations) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 4aefc3a36c..9b2d0b333a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -566,6 +566,10 @@ if (IOS) ${FIREBASE_SOURCE_DIR}/storage/src/include/firebase/storage/listener.h ${FIREBASE_SOURCE_DIR}/storage/src/include/firebase/storage/metadata.h ${FIREBASE_SOURCE_DIR}/storage/src/include/firebase/storage/storage_reference.h) + set(ump_HDRS + ${FIREBASE_SOURCE_DIR}/ump/src/include/firebase/ump.h + ${FIREBASE_SOURCE_DIR}/ump/src/include/firebase/ump/consent_info.h + ${FIREBASE_SOURCE_DIR}/ump/src/include/firebase/ump/types.h) list(APPEND framework_HDRS src/include/firebase/internal/platform.h @@ -580,7 +584,9 @@ if (IOS) ${installations_HDRS} ${messaging_HDRS} ${remote_config_HDRS} - ${storage_HDRS}) + ${storage_HDRS} + ${ump_HDRS} + ) # add framework header files to target target_sources(firebase_app PRIVATE ${framework_HDRS}) diff --git a/settings.gradle b/settings.gradle index 578462cd41..6b2c61e31d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,4 +24,4 @@ include ':app', ':storage', ':storage:storage_resources' ':ump', - ':ump:gma_resources', + ':ump:ump_resources', diff --git a/setup_integration_tests.py b/setup_integration_tests.py index 7c37e12fc8..6565df3d4d 100755 --- a/setup_integration_tests.py +++ b/setup_integration_tests.py @@ -45,6 +45,7 @@ 'messaging/integration_test', 'remote_config/integration_test', 'storage/integration_test', + 'ump/integration_test', ] destinations = sys.argv[1:] if len(sys.argv) > 1 else DEFAULT_DESTINATIONS From cae85123619ad0099add3cf8c73f648bb75c15d5 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 12:02:57 -0700 Subject: [PATCH 11/27] Typo. --- Android/firebase_dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Android/firebase_dependencies.gradle b/Android/firebase_dependencies.gradle index cfba6e93a1..1cc13c4ed3 100644 --- a/Android/firebase_dependencies.gradle +++ b/Android/firebase_dependencies.gradle @@ -54,8 +54,8 @@ def firebaseResourceDependenciesMap = [ 'firestore' : [':firestore:firestore_resources'], 'gma' : [':gma:gma_resources'], 'remote_config' : [':remote_config:remote_config_resources'], - 'storage' : [':storage:storage_resources'] - 'ump' : [':ump:ump_resources'], + 'storage' : [':storage:storage_resources'], + 'ump' : [':ump:ump_resources'] ] def setResourceDependencies(String subproject) { From 7d63e3b79aad8a4b56e52c8bbe3dc71def8de889 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 12:05:10 -0700 Subject: [PATCH 12/27] Format code. --- gma/src/include/firebase/gma/ump/consent_info.h | 3 +++ release_build_files/readme.md | 3 ++- ump/integration_test/src/integration_test.cc | 14 +++++--------- ump/src/android/consent_info_internal_android.cc | 2 +- ump/src/common/consent_info.cc | 2 +- ump/src/common/consent_info_internal.h | 2 +- ump/src/include/firebase/ump/consent_info.h | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gma/src/include/firebase/gma/ump/consent_info.h b/gma/src/include/firebase/gma/ump/consent_info.h index 8f69918ed0..9de3958e01 100644 --- a/gma/src/include/firebase/gma/ump/consent_info.h +++ b/gma/src/include/firebase/gma/ump/consent_info.h @@ -32,6 +32,9 @@ namespace gma { /// /// The User Messaging Platform (UMP) SDK is Google’s option to handle user /// privacy and consent in mobile apps. +/// +/// @deprecated The firebase::gma::ump namespace has been deprecated and +/// renamed to firebase::ump. namespace ump { namespace internal { diff --git a/release_build_files/readme.md b/release_build_files/readme.md index c3596c53b6..a1f55e8c86 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -658,7 +658,8 @@ code. - Changes - UMP: Moved the User Messaging Platform SDK to its own top-level library and to the firebase::ump namespace. The version in the - GMA library (in firebase::gma::ump) will be deprecated soon. + GMA library (in firebase::gma::ump) has been deprecated and will + be removed soon. ### 12.7.0 - Changes diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc index 2ee924b772..bd4ac58e10 100644 --- a/ump/integration_test/src/integration_test.cc +++ b/ump/integration_test/src/integration_test.cc @@ -135,9 +135,7 @@ void FirebaseUmpTest::SetUp() { ASSERT_NE(consent_info_, nullptr); } -void FirebaseUmpTest::TearDown() { - TerminateUmp(); -} +void FirebaseUmpTest::TearDown() { TerminateUmp(); } // Tests for User Messaging Platform TEST_F(FirebaseUmpTest, TestUmpInitialization) { @@ -750,9 +748,8 @@ TEST_F(FirebaseUmpTest, TestUmpMethodsReturnOperationInProgress) { consent_info_->RequestConsentInfoUpdate(params); firebase::Future future_request_2 = consent_info_->RequestConsentInfoUpdate(params); - WaitForCompletion( - future_request_2, "RequestConsentInfoUpdate second", - firebase::ump::kConsentRequestErrorOperationInProgress); + WaitForCompletion(future_request_2, "RequestConsentInfoUpdate second", + firebase::ump::kConsentRequestErrorOperationInProgress); WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first", {firebase::ump::kConsentRequestSuccess, firebase::ump::kConsentRequestErrorNetwork}); @@ -785,9 +782,8 @@ TEST_F(FirebaseUmpTest, TestUmpMethodsReturnOperationInProgressWithUI) { consent_info_->RequestConsentInfoUpdate(params); firebase::Future future_request_2 = consent_info_->RequestConsentInfoUpdate(params); - WaitForCompletion( - future_request_2, "RequestConsentInfoUpdate second", - firebase::ump::kConsentRequestErrorOperationInProgress); + WaitForCompletion(future_request_2, "RequestConsentInfoUpdate second", + firebase::ump::kConsentRequestErrorOperationInProgress); WaitForCompletion(future_request_1, "RequestConsentInfoUpdate first"); firebase::Future future_load_1 = consent_info_->LoadConsentForm(); diff --git a/ump/src/android/consent_info_internal_android.cc b/ump/src/android/consent_info_internal_android.cc index defb784952..6f03f2a7d7 100644 --- a/ump/src/android/consent_info_internal_android.cc +++ b/ump/src/android/consent_info_internal_android.cc @@ -25,8 +25,8 @@ #include "app/src/thread.h" #include "app/src/util_android.h" #include "firebase/internal/common.h" -#include "ump/ump_resources.h" #include "ump/src/android/ump_android.h" +#include "ump/ump_resources.h" namespace firebase { namespace ump { diff --git a/ump/src/common/consent_info.cc b/ump/src/common/consent_info.cc index 3b008c11bf..a93872484c 100644 --- a/ump/src/common/consent_info.cc +++ b/ump/src/common/consent_info.cc @@ -18,8 +18,8 @@ #include "app/src/assert.h" #include "firebase/app.h" -#include "firebase/ump.h" #include "firebase/internal/platform.h" +#include "firebase/ump.h" #include "ump//src/common/consent_info_internal.h" namespace firebase { diff --git a/ump/src/common/consent_info_internal.h b/ump/src/common/consent_info_internal.h index f4cc530dda..0578478653 100644 --- a/ump/src/common/consent_info_internal.h +++ b/ump/src/common/consent_info_internal.h @@ -20,9 +20,9 @@ #include "app/src/cleanup_notifier.h" #include "app/src/reference_counted_future_impl.h" #include "firebase/future.h" +#include "firebase/internal/platform.h" #include "firebase/ump.h" #include "firebase/ump/types.h" -#include "firebase/internal/platform.h" #if FIREBASE_PLATFORM_ANDROID #include diff --git a/ump/src/include/firebase/ump/consent_info.h b/ump/src/include/firebase/ump/consent_info.h index 40d71d431f..10d670c821 100644 --- a/ump/src/include/firebase/ump/consent_info.h +++ b/ump/src/include/firebase/ump/consent_info.h @@ -19,8 +19,8 @@ #include "firebase/app.h" #include "firebase/future.h" -#include "firebase/ump/types.h" #include "firebase/internal/platform.h" +#include "firebase/ump/types.h" #if FIREBASE_PLATFORM_ANDROID #include From 2f73e9ad6b74f2e9be962dd56b5b81b4fa23620c Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 12:09:22 -0700 Subject: [PATCH 13/27] Add deprecation flags. --- .../include/firebase/gma/ump/consent_info.h | 56 +++++++++++++++++++ gma/src/include/firebase/gma/ump/types.h | 17 ++++++ 2 files changed, 73 insertions(+) diff --git a/gma/src/include/firebase/gma/ump/consent_info.h b/gma/src/include/firebase/gma/ump/consent_info.h index 9de3958e01..40b80baef3 100644 --- a/gma/src/include/firebase/gma/ump/consent_info.h +++ b/gma/src/include/firebase/gma/ump/consent_info.h @@ -20,6 +20,7 @@ #include "firebase/app.h" #include "firebase/future.h" #include "firebase/gma/ump/types.h" +#include "firebase/internal/common.h" #include "firebase/internal/platform.h" #if FIREBASE_PLATFORM_ANDROID @@ -49,6 +50,8 @@ class ConsentInfoInternal; /// /// This class contains all of the methods necessary for obtaining /// consent from the user. +/// +/// @deprecated This class has been moved to the firebase::ump namespace. class ConsentInfo { public: /// Shut down the User Messaging Platform Consent SDK. @@ -67,6 +70,9 @@ class ConsentInfo { /// initialized, nullptr otherwise. Each call to GetInstance() will return the /// same pointer; when you are finished using the SDK, you can delete the /// pointer and the UMP SDK will shut down. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED static ConsentInfo* GetInstance(const ::firebase::App& app, InitResult* init_result_out = nullptr); @@ -96,6 +102,7 @@ class ConsentInfo { /// initialized, nullptr otherwise. Each call to GetInstance() will return the /// same pointer; when you are finished using the SDK, you can delete the /// pointer and the UMP SDK will shut down. + FIREBASE_DEPRECATED static ConsentInfo* GetInstance(JNIEnv* jni_env, jobject activity, InitResult* init_result_out = nullptr); @@ -104,6 +111,7 @@ class ConsentInfo { // existing ConsentInfo instance after it's first initialized. Returns nullptr // if no instance has been created yet; make sure you have called // GetInstance(JNIEnv*, jobject) first. + FIREBASE_DEPRECATED static ConsentInfo* GetInstance(); #endif // defined(DOXYGEN) #endif // FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) @@ -123,12 +131,18 @@ class ConsentInfo { /// /// @note Once any overload of ConsentInfo::GetInstance has been called, you /// can use this method to obtain the same instance again. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED static ConsentInfo* GetInstance(InitResult* init_result_out = nullptr); #endif // !defined(__ANDROID__) || defined(DOXYGEN) /// The user’s consent status. This value defaults to kConsentStatusUnknown /// until RequestConsentInfoUpdate() is called, and defaults to the previous /// session’s value until RequestConsentInfoUpdate() completes. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED ConsentStatus GetConsentStatus(); /// Requests consent information update. Must be called in every app session @@ -137,20 +151,35 @@ class ConsentInfo { /// updated immediately to hold the consent state from the previous app /// session, if one exists. GetConsentStatus() and CanRequestAds() may be /// updated again immediately before the returned future is completed. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future RequestConsentInfoUpdate(const ConsentRequestParameters& params); /// Get the Future from the most recent call to RequestConsentInfoUpdate(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future RequestConsentInfoUpdateLastResult(); /// Consent form status. This value defaults to kConsentFormStatusUnknown and /// requires a call to RequestConsentInfoUpdate() to update. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED ConsentFormStatus GetConsentFormStatus(); /// Loads a consent form. Returns an error if the consent form is unavailable /// or cannot be loaded. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future LoadConsentForm(); /// Get the Future from the most recent call to LoadConsentForm(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future LoadConsentFormLastResult(); /// Presents the full screen consent form using the given FormParent, which is @@ -166,9 +195,15 @@ class ConsentInfo { /// /// @note You must call LoadConsentForm() and wait for it to complete before /// calling this method. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future ShowConsentForm(FormParent parent); /// Get the Future from the most recent call to ShowConsentForm(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future ShowConsentFormLastResult(); /// Loads a consent form and immediately presents it using the given @@ -183,14 +218,23 @@ class ConsentInfo { /// /// @param[in] parent A FormParent, which is an Activity object on Android and /// a UIViewController object on iOS. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future LoadAndShowConsentFormIfRequired(FormParent parent); /// Get the Future from the most recent call to /// LoadAndShowConsentFormIfRequired(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future LoadAndShowConsentFormIfRequiredLastResult(); /// Check whether the privacy options form needs to be displayed. /// This is updated by RequestConsentInfoUpdate(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED PrivacyOptionsRequirementStatus GetPrivacyOptionsRequirementStatus(); /// If GetPrivacyOptionsRequirementStatus() is @@ -209,19 +253,31 @@ class ConsentInfo { /// /// @param[in] parent A FormParent, which is an Activity object on Android and /// a UIViewController object on iOS. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future ShowPrivacyOptionsForm(FormParent parent); /// Get the Future from the most recent call to ShowPrivacyOptionsForm(). + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED Future ShowPrivacyOptionsFormLastResult(); /// Indicates whether the app has completed the necessary steps for gathering /// updated user consent. Returns true if RequestConsentInfoUpdate() has been /// called and GetConsentStatus returns either kConsentStatusNotRequired or /// kConsentStatusObtained. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED bool CanRequestAds(); /// Clears all consent state from persistent storage. This can be used in /// development to simulate a new installation. + /// + /// @deprecated This class has been moved to the firebase::ump namespace. + FIREBASE_DEPRECATED void Reset(); private: diff --git a/gma/src/include/firebase/gma/ump/types.h b/gma/src/include/firebase/gma/ump/types.h index 0684858582..40e6e23f3a 100644 --- a/gma/src/include/firebase/gma/ump/types.h +++ b/gma/src/include/firebase/gma/ump/types.h @@ -22,6 +22,7 @@ #include #include "firebase/internal/platform.h" +#include "firebase/internal/common.h" #if FIREBASE_PLATFORM_ANDROID #include @@ -37,6 +38,8 @@ namespace gma { namespace ump { /// Debug values for testing geography. +/// +/// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentDebugGeography { /// Disable geography debugging. kConsentDebugGeographyDisabled = 0, @@ -49,6 +52,8 @@ enum ConsentDebugGeography { /// Debug settings for `ConsentInfo::RequestConsentInfoUpdate()`. These let you /// force a specific geographic location. Be sure to include debug device IDs to /// enable this on hardware. Debug features are always enabled for simulators. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. struct ConsentDebugSettings { /// Create a default debug setting, with debugging disabled. ConsentDebugSettings() : debug_geography(kConsentDebugGeographyDisabled) {} @@ -61,6 +66,8 @@ struct ConsentDebugSettings { }; /// Parameters for the `ConsentInfo::RequestConsentInfoUpdate()` operation. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. struct ConsentRequestParameters { ConsentRequestParameters() : tag_for_under_age_of_consent(false) {} @@ -92,6 +99,8 @@ typedef void* FormParent; // FIREBASE_PLATFORM_TVOS /// Consent status values. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. enum ConsentStatus { /// Unknown status, e.g. prior to calling Request, or if the request fails. kConsentStatusUnknown = 0, @@ -104,6 +113,8 @@ enum ConsentStatus { }; /// Errors that can occur during a RequestConsentInfoUpdate operation. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. enum ConsentRequestError { /// The operation succeeded. kConsentRequestSuccess = 0, @@ -126,6 +137,8 @@ enum ConsentRequestError { }; /// Status of the consent form, whether it is available to show or not. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. enum ConsentFormStatus { /// Status is unknown. Call `ConsentInfo::RequestConsentInfoUpdate()` to /// update this. @@ -139,6 +152,8 @@ enum ConsentFormStatus { }; /// Errors when loading or showing the consent form. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. enum ConsentFormError { /// The operation succeeded. kConsentFormSuccess = 0, @@ -161,6 +176,8 @@ enum ConsentFormError { }; /// Whether the privacy options need to be displayed. +/// +/// @deprecated This struct has been moved to the firebase::ump namespace. enum PrivacyOptionsRequirementStatus { /// Privacy options requirement status is unknown. Call /// `ConsentInfo::RequestConsentInfoUpdate()` to update. From ea2a03ee199c817f74c366bdee073abc3f701181 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 8 May 2025 12:11:48 -0700 Subject: [PATCH 14/27] Fix typo. --- settings.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 6b2c61e31d..ad2d909d3d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,6 @@ include ':app', ':remote_config', ':remote_config:remote_config_resources', ':storage', - ':storage:storage_resources' + ':storage:storage_resources', ':ump', - ':ump:ump_resources', + ':ump:ump_resources' From c03808d1b0961e1298f854d266f38c45fb1db204 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 May 2025 10:18:48 -0700 Subject: [PATCH 15/27] Fix deprecation comments. --- gma/src/include/firebase/gma/ump/types.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gma/src/include/firebase/gma/ump/types.h b/gma/src/include/firebase/gma/ump/types.h index 40e6e23f3a..d107c8ea87 100644 --- a/gma/src/include/firebase/gma/ump/types.h +++ b/gma/src/include/firebase/gma/ump/types.h @@ -100,7 +100,7 @@ typedef void* FormParent; /// Consent status values. /// -/// @deprecated This struct has been moved to the firebase::ump namespace. +/// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentStatus { /// Unknown status, e.g. prior to calling Request, or if the request fails. kConsentStatusUnknown = 0, @@ -114,7 +114,7 @@ enum ConsentStatus { /// Errors that can occur during a RequestConsentInfoUpdate operation. /// -/// @deprecated This struct has been moved to the firebase::ump namespace. +/// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentRequestError { /// The operation succeeded. kConsentRequestSuccess = 0, @@ -138,7 +138,7 @@ enum ConsentRequestError { /// Status of the consent form, whether it is available to show or not. /// -/// @deprecated This struct has been moved to the firebase::ump namespace. +/// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentFormStatus { /// Status is unknown. Call `ConsentInfo::RequestConsentInfoUpdate()` to /// update this. @@ -153,7 +153,7 @@ enum ConsentFormStatus { /// Errors when loading or showing the consent form. /// -/// @deprecated This struct has been moved to the firebase::ump namespace. +/// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentFormError { /// The operation succeeded. kConsentFormSuccess = 0, @@ -177,7 +177,7 @@ enum ConsentFormError { /// Whether the privacy options need to be displayed. /// -/// @deprecated This struct has been moved to the firebase::ump namespace. +/// @deprecated This enum has been moved to the firebase::ump namespace. enum PrivacyOptionsRequirementStatus { /// Privacy options requirement status is unknown. Call /// `ConsentInfo::RequestConsentInfoUpdate()` to update. From 26bbc9fd9c933d35486cc918cb200f4deaa5a9a6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 May 2025 10:21:08 -0700 Subject: [PATCH 16/27] Remove GMA-specific integration test setup. --- ump/integration_test/AndroidManifest.xml | 4 ---- ump/integration_test/Info.plist | 4 ---- 2 files changed, 8 deletions(-) diff --git a/ump/integration_test/AndroidManifest.xml b/ump/integration_test/AndroidManifest.xml index 8aa98a1958..0a92713e97 100644 --- a/ump/integration_test/AndroidManifest.xml +++ b/ump/integration_test/AndroidManifest.xml @@ -23,10 +23,6 @@ - - - GADApplicationIdentifier - ca-app-pub-3940256099942544~1458002511 CFBundleDevelopmentRegion en CFBundleExecutable @@ -24,8 +22,6 @@ UILaunchStoryboardName LaunchScreen - NSUserTrackingUsageDescription - This identifier will be used to deliver personalized ads to you. CFBundleURLTypes From be00b19c90a3c9da8787763678df75872f1c11d6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 May 2025 10:21:17 -0700 Subject: [PATCH 17/27] Remove leftover GMA references. --- ump/CMakeLists.txt | 1 - ump/ump_additional.pro | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 ump/ump_additional.pro diff --git a/ump/CMakeLists.txt b/ump/CMakeLists.txt index 5c24cf8e08..2739decefa 100644 --- a/ump/CMakeLists.txt +++ b/ump/CMakeLists.txt @@ -96,7 +96,6 @@ elseif(IOS) setup_pod_headers( firebase_ump POD_NAMES - Google-Mobile-Ads-SDK GoogleUserMessagingPlatform ) diff --git a/ump/ump_additional.pro b/ump/ump_additional.pro deleted file mode 100644 index 57edbf9017..0000000000 --- a/ump/ump_additional.pro +++ /dev/null @@ -1,2 +0,0 @@ -# Additional ProGuard rules needed for the AdMob library. --keep class com.google.ads.mediation.admob.AdMobAdapter { *; } From c96d5c2392371c2ca6303e67b94d2fd62301689f Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 May 2025 10:46:05 -0700 Subject: [PATCH 18/27] Fix build error. --- ump/integration_test/src/integration_test.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc index bd4ac58e10..cf3f279feb 100644 --- a/ump/integration_test/src/integration_test.cc +++ b/ump/integration_test/src/integration_test.cc @@ -80,6 +80,9 @@ class FirebaseUmpTest : public FirebaseTest { void InitializeUmp(ResetOption reset = kReset); void TerminateUmp(ResetOption reset = kReset); + static void SetUpTestSuite(); + static void TearDownTestSuite(); + void SetUp() override; void TearDown() override; From e6d05ab7c906373ab1f03a0da22841b12aa8f3a3 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Mon, 12 May 2025 11:26:38 -0700 Subject: [PATCH 19/27] Add missing static memory allocation. --- ump/integration_test/src/integration_test.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc index cf3f279feb..c8fd3cd29f 100644 --- a/ump/integration_test/src/integration_test.cc +++ b/ump/integration_test/src/integration_test.cc @@ -91,6 +91,8 @@ class FirebaseUmpTest : public FirebaseTest { firebase::ump::ConsentInfo* consent_info_; }; +firebase::App* FirebaseUmpTest::shared_app_ = nullptr; + void FirebaseUmpTest::SetUpTestSuite() { LogDebug("Initialize Firebase App."); From 193e428509276c86c99e1c87369356fc5f65bd95 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Tue, 13 May 2025 13:59:09 -0700 Subject: [PATCH 20/27] Add resources for UMP tests. --- .../ump/GoogleService-Info.plist.gpg | Bin 0 -> 663 bytes .../gha-encrypted/ump/google-services.json.gpg | Bin 0 -> 953 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/gha-encrypted/ump/GoogleService-Info.plist.gpg create mode 100644 scripts/gha-encrypted/ump/google-services.json.gpg diff --git a/scripts/gha-encrypted/ump/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/ump/GoogleService-Info.plist.gpg new file mode 100644 index 0000000000000000000000000000000000000000..65b703ce7e8841cd231a8eeb0b355a6631c9f463 GIT binary patch literal 663 zcmV;I0%-k=4Fm}T0-fzaCMf$rivQB-0p|fHWyO^&Ync6x>Efpw?qdT3?Bq zjPBP~k2>%=QT6e(ZG>MNm7#5O(Tt)6z3idw07jl=zmH+QdQwr!bWyx1XVPt(Y69$O zD4AO!?IvhRONC0FbWiJT`zhZmbn27_AYEmd>+nnOdpgf=n_!lIL zK)jN{X+d{4{S4FtlNI9nxr_#!x(Q%I``z*)J2;DCGM_|iX3|-W)CQ5^yN1O75hO)q zOAm}cNOC&k9UUh(06a3A3f}Rm%cuh@98<4}TzGKY(o zb$4E@E=$QI0G*5t)3w8Lpzo)!43vQH3lnr~U&CRUz|*s$yPy~p$lLU7|2?8b{0dPY xG>ERw<<0y28k literal 0 HcmV?d00001 diff --git a/scripts/gha-encrypted/ump/google-services.json.gpg b/scripts/gha-encrypted/ump/google-services.json.gpg new file mode 100644 index 0000000000000000000000000000000000000000..c46f596425a5955ec5b958766883ee90eb2ecf91 GIT binary patch literal 953 zcmV;q14jIe4Fm}T0?5jj8X$yf(Erlu0o=SNVA9@O)Q#w6U2MVR(@sDoEKw4ruNO3O zPmJZ;D5%EpUdQb|p#VZcUSnlVF@HZ;Wo}Mq^r98p=hBMmONvOFn$0sn^L@iCqmP0F=UD9qsJblrIkqLol4tEpjp=eNY6-X z3b1{x7dV}KDG(ANS!?UAG=_uE%pR_G)fWWN=y3INqb`x*r;Qh`aW8c@%WR>J#-RSH z_yZ@%%_g%i!9NC5P8?4F*Q|}lID-2sQh`i3YbzJbE9Cv3R^QvR9b;>>avs%oXYmSZ z(s}B$Z%QQ#9*42A>|_%5lapchw(xLZ$O#HE7)Vtk;xn~`iH2pd{n2prmB}cn2+yHM z_KWm!Y7DPfg{`p<;raH;-q*N2tRG0zIdUPVn>knZifwyjUFtzxxP+pI2SIo$vq(7K zH(MHzZk5pMUByZtm?FxuJ5p@_pu|0(kvV5~7acIy|Ek-FrC2v9Xp<3(e<7|m<^>?% zg0PrAonln~Hd;|_%R*5#vo4#5d_|1?ZRSQzeZKDpUbZCyk|Y0PAtCl1xHz#^FFSZ^ z!1yU~KTei^?nVUonbM^qeEz;g^!3Dg#mNS;O`_ULM_J63hHe;7BL&mO>=K zwc<{Q4XsD%oc#^bfC`Biz~;ot-)Ku86zZ3) z-lCqPSZH;ue>aJDQ`eQ3FPi6QjYI@Rf&YQRqoCJu<<)m$PbpSakN-)z+b>_w;MdEA zM-w?Vvsh7EOK4T_iJEF2mPPu~{F~3me5AD}r)>`h)%;#B9;Mn=PX0M3s`>C?WSNK9vak2` zenu+TM-u+s2hgi?MXENJ{8#AIW@jq7BUic?=b7+v3gOs9KYZp literal 0 HcmV?d00001 From 0c4bf666ade2c969ba82e87a64d6f2db03de70f6 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Tue, 13 May 2025 15:55:57 -0700 Subject: [PATCH 21/27] Remove extraneous include. --- ump/src/android/consent_info_internal_android.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/ump/src/android/consent_info_internal_android.cc b/ump/src/android/consent_info_internal_android.cc index 6f03f2a7d7..17ea1c56c8 100644 --- a/ump/src/android/consent_info_internal_android.cc +++ b/ump/src/android/consent_info_internal_android.cc @@ -25,7 +25,6 @@ #include "app/src/thread.h" #include "app/src/util_android.h" #include "firebase/internal/common.h" -#include "ump/src/android/ump_android.h" #include "ump/ump_resources.h" namespace firebase { From c9529b1394f1e10d0fbaeafc2052311e69d2334c Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Tue, 13 May 2025 15:57:21 -0700 Subject: [PATCH 22/27] Add application ID back. --- ump/integration_test/AndroidManifest.xml | 4 ++++ ump/integration_test/Info.plist | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ump/integration_test/AndroidManifest.xml b/ump/integration_test/AndroidManifest.xml index 0a92713e97..8aa98a1958 100644 --- a/ump/integration_test/AndroidManifest.xml +++ b/ump/integration_test/AndroidManifest.xml @@ -23,6 +23,10 @@ + + + GADApplicationIdentifier + ca-app-pub-3940256099942544~1458002511 CFBundleDevelopmentRegion en CFBundleExecutable From 977d3abae4f62bb4f33a4437261966e830b1face Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Tue, 13 May 2025 22:06:51 -0700 Subject: [PATCH 23/27] Add missing static memory allocations. --- ump/src/android/consent_info_internal_android.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ump/src/android/consent_info_internal_android.cc b/ump/src/android/consent_info_internal_android.cc index 17ea1c56c8..93b8ae05bb 100644 --- a/ump/src/android/consent_info_internal_android.cc +++ b/ump/src/android/consent_info_internal_android.cc @@ -31,6 +31,13 @@ namespace firebase { namespace ump { namespace internal { +ConsentInfoInternalAndroid* ConsentInfoInternalAndroid::s_instance = nullptr; +firebase::Mutex ConsentInfoInternalAndroid::s_instance_mutex; + +::firebase::Mutex g_cached_ump_embedded_files_mutex; +std::vector<::firebase::internal::EmbeddedFile>* g_cached_ump_embedded_files = + nullptr; + // clang-format off #define CONSENTINFOHELPER_METHODS(X) \ X(Constructor, "", "(JLandroid/app/Activity;)V"), \ From 89ed214f864c9b99c721c01daa2ca58c4058c3f0 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 14 May 2025 12:53:13 -0700 Subject: [PATCH 24/27] Format code. --- gma/src/include/firebase/gma/ump/types.h | 18 +++++++++--------- ump/integration_test/src/integration_test.cc | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gma/src/include/firebase/gma/ump/types.h b/gma/src/include/firebase/gma/ump/types.h index d107c8ea87..cce119f56b 100644 --- a/gma/src/include/firebase/gma/ump/types.h +++ b/gma/src/include/firebase/gma/ump/types.h @@ -21,8 +21,8 @@ #include #include -#include "firebase/internal/platform.h" #include "firebase/internal/common.h" +#include "firebase/internal/platform.h" #if FIREBASE_PLATFORM_ANDROID #include @@ -38,7 +38,7 @@ namespace gma { namespace ump { /// Debug values for testing geography. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentDebugGeography { /// Disable geography debugging. @@ -52,7 +52,7 @@ enum ConsentDebugGeography { /// Debug settings for `ConsentInfo::RequestConsentInfoUpdate()`. These let you /// force a specific geographic location. Be sure to include debug device IDs to /// enable this on hardware. Debug features are always enabled for simulators. -/// +/// /// @deprecated This struct has been moved to the firebase::ump namespace. struct ConsentDebugSettings { /// Create a default debug setting, with debugging disabled. @@ -66,7 +66,7 @@ struct ConsentDebugSettings { }; /// Parameters for the `ConsentInfo::RequestConsentInfoUpdate()` operation. -/// +/// /// @deprecated This struct has been moved to the firebase::ump namespace. struct ConsentRequestParameters { ConsentRequestParameters() : tag_for_under_age_of_consent(false) {} @@ -99,7 +99,7 @@ typedef void* FormParent; // FIREBASE_PLATFORM_TVOS /// Consent status values. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentStatus { /// Unknown status, e.g. prior to calling Request, or if the request fails. @@ -113,7 +113,7 @@ enum ConsentStatus { }; /// Errors that can occur during a RequestConsentInfoUpdate operation. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentRequestError { /// The operation succeeded. @@ -137,7 +137,7 @@ enum ConsentRequestError { }; /// Status of the consent form, whether it is available to show or not. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentFormStatus { /// Status is unknown. Call `ConsentInfo::RequestConsentInfoUpdate()` to @@ -152,7 +152,7 @@ enum ConsentFormStatus { }; /// Errors when loading or showing the consent form. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum ConsentFormError { /// The operation succeeded. @@ -176,7 +176,7 @@ enum ConsentFormError { }; /// Whether the privacy options need to be displayed. -/// +/// /// @deprecated This enum has been moved to the firebase::ump namespace. enum PrivacyOptionsRequirementStatus { /// Privacy options requirement status is unknown. Call diff --git a/ump/integration_test/src/integration_test.cc b/ump/integration_test/src/integration_test.cc index c8fd3cd29f..97ff0c3976 100644 --- a/ump/integration_test/src/integration_test.cc +++ b/ump/integration_test/src/integration_test.cc @@ -82,7 +82,7 @@ class FirebaseUmpTest : public FirebaseTest { static void SetUpTestSuite(); static void TearDownTestSuite(); - + void SetUp() override; void TearDown() override; From b3659330bf97d7e706c5267aecb9621f789b9e2d Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Wed, 14 May 2025 13:31:01 -0700 Subject: [PATCH 25/27] Add deprecation note. --- release_build_files/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index a1f55e8c86..48e1b565ba 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -10,7 +10,7 @@ on *iOS* and *Android*: * Firebase Dynamic Links (deprecated SDK) * Cloud Firestore * Firebase Functions -* Google Mobile Ads +* Google Mobile Ads (deprecated SDK) * Firebase Installations * Firebase Instance ID (deprecated SDK) * Firebase Realtime Database From ba503a20e8aa6b42c1626ce95b37d3cad363fd42 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 15 May 2025 12:02:54 -0700 Subject: [PATCH 26/27] Add verbose flag. --- .github/workflows/integration_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index a600257dc8..c03e1cc30e 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -353,7 +353,7 @@ jobs: elif [[ "${{ github.event.inputs.firestore_dep_source }}" ]]; then additional_flags+=(--cmake_flag=-DFIRESTORE_DEP_SOURCE=${{ github.event.inputs.firestore_dep_source }}) fi - python scripts/gha/build_testapps.py --p Desktop \ + VERBOSE=1 python scripts/gha/build_testapps.py --p Desktop \ --t ${{ needs.check_and_prepare.outputs.apis }} \ --output_directory "${{ github.workspace }}" \ --artifact_name "desktop-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.ssl_variant }}" \ From ab43a278228824da6da41340391e4764effdcba0 Mon Sep 17 00:00:00 2001 From: Jon Simantov Date: Thu, 15 May 2025 13:15:47 -0700 Subject: [PATCH 27/27] Add missing library to packaged SDK cmake file. --- release_build_files/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/release_build_files/CMakeLists.txt b/release_build_files/CMakeLists.txt index e9e341e0aa..594440937d 100644 --- a/release_build_files/CMakeLists.txt +++ b/release_build_files/CMakeLists.txt @@ -103,6 +103,7 @@ add_firebase_target(firebase_performance) add_firebase_target(firebase_remote_config) add_firebase_target(firebase_storage) add_firebase_target(firebase_testlab) +add_firebase_target(firebase_ump) # Auth on Linux desktop has an additional dependency on libsecret, # which needs to be added. If it cannot be found, we don't want to